summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/android/app/src/main/AndroidManifest.xml1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt224
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt45
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt26
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt39
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt25
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/NativeInput.kt416
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/YuzuInputDevice.kt93
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/YuzuVibrator.kt76
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/AnalogDirection.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/ButtonName.kt19
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/InputType.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeAnalog.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeButton.kt38
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeTrigger.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NpadStyleIndex.kt30
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/PlayerInput.kt83
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt15
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/AnalogInputSetting.kt31
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/ButtonInputSetting.kt29
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt9
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/InputProfileSetting.kt32
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/InputSetting.kt134
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/IntSingleChoiceSetting.kt38
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/ModifierInputSetting.kt31
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt233
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt15
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt9
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputDialogFragment.kt300
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileAdapter.kt68
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileDialogFragment.kt148
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/NewInputProfileDialogFragment.kt79
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt58
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt247
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt (renamed from src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt)97
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt85
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt794
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSearchFragment.kt (renamed from src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt)32
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsViewModel.kt (renamed from src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt)43
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt24
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/HeaderViewHolder.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/InputProfileViewHolder.kt34
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/InputViewHolder.kt60
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt26
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt81
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt23
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt29
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt24
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt91
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CoreErrorDialogFragment.kt47
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt18
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt229
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt46
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt102
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt52
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt46
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt101
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt25
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt15
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt71
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt67
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt456
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LifecycleUtils.kt38
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt15
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ParamPackage.kt141
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt33
-rw-r--r--src/android/app/src/main/jni/CMakeLists.txt1
-rw-r--r--src/android/app/src/main/jni/android_config.cpp141
-rw-r--r--src/android/app/src/main/jni/android_config.h7
-rw-r--r--src/android/app/src/main/jni/emu_window/emu_window.cpp43
-rw-r--r--src/android/app/src/main/jni/emu_window/emu_window.h15
-rw-r--r--src/android/app/src/main/jni/native.cpp175
-rw-r--r--src/android/app/src/main/jni/native.h5
-rw-r--r--src/android/app/src/main/jni/native_config.cpp117
-rw-r--r--src/android/app/src/main/jni/native_input.cpp629
-rw-r--r--src/android/app/src/main/res/drawable/button_anim.xml142
-rw-r--r--src/android/app/src/main/res/drawable/ic_controller_disconnected.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_more_vert.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_new_label.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_overlay.xml21
-rw-r--r--src/android/app/src/main/res/drawable/ic_share.xml9
-rw-r--r--src/android/app/src/main/res/drawable/stick_one_direction_anim.xml118
-rw-r--r--src/android/app/src/main/res/drawable/stick_two_direction_anim.xml173
-rw-r--r--src/android/app/src/main/res/layout-ldrtl/list_item_setting_input.xml63
-rw-r--r--src/android/app/src/main/res/layout/card_driver_option.xml9
-rw-r--r--src/android/app/src/main/res/layout/card_folder.xml3
-rw-r--r--src/android/app/src/main/res/layout/card_game.xml3
-rw-r--r--src/android/app/src/main/res/layout/card_simple_outlined.xml3
-rw-r--r--src/android/app/src/main/res/layout/dialog_input_profiles.xml6
-rw-r--r--src/android/app/src/main/res/layout/dialog_mapping.xml26
-rw-r--r--src/android/app/src/main/res/layout/fragment_game_properties.xml3
-rw-r--r--src/android/app/src/main/res/layout/list_item_input_profile.xml74
-rw-r--r--src/android/app/src/main/res/layout/list_item_setting_input.xml63
-rw-r--r--src/android/app/src/main/res/menu/menu_in_game.xml7
-rw-r--r--src/android/app/src/main/res/menu/menu_input_options.xml34
-rw-r--r--src/android/app/src/main/res/navigation/settings_navigation.xml2
-rw-r--r--src/android/app/src/main/res/values-w600dp/dimens.xml2
-rw-r--r--src/android/app/src/main/res/values/dimens.xml2
-rw-r--r--src/android/app/src/main/res/values/strings.xml93
-rw-r--r--src/audio_core/sink/cubeb_sink.cpp4
-rw-r--r--src/audio_core/sink/sink_stream.cpp4
-rw-r--r--src/common/android/id_cache.cpp163
-rw-r--r--src/common/android/id_cache.h24
-rw-r--r--src/common/demangle.cpp4
-rw-r--r--src/common/host_memory.cpp4
-rw-r--r--src/common/page_table.cpp4
-rw-r--r--src/common/scope_exit.h66
-rw-r--r--src/common/settings_input.h4
-rw-r--r--src/core/CMakeLists.txt181
-rw-r--r--src/core/core.cpp21
-rw-r--r--src/core/cpu_manager.cpp4
-rw-r--r--src/core/device_memory_manager.inc4
-rw-r--r--src/core/file_sys/control_metadata.h4
-rw-r--r--src/core/file_sys/fs_directory.h4
-rw-r--r--src/core/file_sys/fs_path_utility.h12
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp4
-rw-r--r--src/core/file_sys/program_metadata.cpp4
-rw-r--r--src/core/file_sys/system_archive/shared_font.cpp2
-rw-r--r--src/core/hle/kernel/k_client_session.cpp8
-rw-r--r--src/core/hle/kernel/k_page_table_base.cpp40
-rw-r--r--src/core/hle/kernel/k_process.cpp16
-rw-r--r--src/core/hle/kernel/k_server_session.cpp36
-rw-r--r--src/core/hle/kernel/k_thread.h4
-rw-r--r--src/core/hle/kernel/k_thread_local_page.cpp4
-rw-r--r--src/core/hle/kernel/k_transfer_memory.cpp4
-rw-r--r--src/core/hle/kernel/kernel.cpp69
-rw-r--r--src/core/hle/kernel/kernel.h4
-rw-r--r--src/core/hle/kernel/svc/svc_code_memory.cpp4
-rw-r--r--src/core/hle/kernel/svc/svc_device_address_space.cpp4
-rw-r--r--src/core/hle/kernel/svc/svc_event.cpp4
-rw-r--r--src/core/hle/kernel/svc/svc_ipc.cpp8
-rw-r--r--src/core/hle/kernel/svc/svc_port.cpp8
-rw-r--r--src/core/hle/kernel/svc/svc_resource_limit.cpp4
-rw-r--r--src/core/hle/kernel/svc/svc_session.cpp4
-rw-r--r--src/core/hle/kernel/svc/svc_synchronization.cpp4
-rw-r--r--src/core/hle/kernel/svc/svc_thread.cpp4
-rw-r--r--src/core/hle/kernel/svc/svc_transfer_memory.cpp4
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp5
-rw-r--r--src/core/hle/service/am/am.cpp10
-rw-r--r--src/core/hle/service/am/am.h6
-rw-r--r--src/core/hle/service/am/applet.h6
-rw-r--r--src/core/hle/service/am/applet_data_broker.cpp4
-rw-r--r--src/core/hle/service/am/applet_manager.cpp18
-rw-r--r--src/core/hle/service/am/display_layer_manager.cpp151
-rw-r--r--src/core/hle/service/am/display_layer_manager.h62
-rw-r--r--src/core/hle/service/am/frontend/applet_web_browser.cpp2
-rw-r--r--src/core/hle/service/am/library_applet_storage.cpp4
-rw-r--r--src/core/hle/service/am/managed_layer_holder.cpp59
-rw-r--r--src/core/hle/service/am/managed_layer_holder.h32
-rw-r--r--src/core/hle/service/am/process.cpp4
-rw-r--r--src/core/hle/service/am/service/all_system_applet_proxies_service.cpp13
-rw-r--r--src/core/hle/service/am/service/all_system_applet_proxies_service.h8
-rw-r--r--src/core/hle/service/am/service/application_functions.cpp30
-rw-r--r--src/core/hle/service/am/service/application_functions.h1
-rw-r--r--src/core/hle/service/am/service/application_proxy.cpp9
-rw-r--r--src/core/hle/service/am/service/application_proxy.h3
-rw-r--r--src/core/hle/service/am/service/application_proxy_service.cpp7
-rw-r--r--src/core/hle/service/am/service/application_proxy_service.h7
-rw-r--r--src/core/hle/service/am/service/display_controller.cpp6
-rw-r--r--src/core/hle/service/am/service/library_applet_creator.cpp2
-rw-r--r--src/core/hle/service/am/service/library_applet_proxy.cpp10
-rw-r--r--src/core/hle/service/am/service/library_applet_proxy.h3
-rw-r--r--src/core/hle/service/am/service/library_applet_self_accessor.cpp17
-rw-r--r--src/core/hle/service/am/service/library_applet_self_accessor.h2
-rw-r--r--src/core/hle/service/am/service/self_controller.cpp53
-rw-r--r--src/core/hle/service/am/service/self_controller.h3
-rw-r--r--src/core/hle/service/am/service/system_applet_proxy.cpp10
-rw-r--r--src/core/hle/service/am/service/system_applet_proxy.h3
-rw-r--r--src/core/hle/service/am/service/window_controller.cpp2
-rw-r--r--src/core/hle/service/am/system_buffer_manager.cpp80
-rw-r--r--src/core/hle/service/am/system_buffer_manager.h52
-rw-r--r--src/core/hle/service/audio/audctl.cpp201
-rw-r--r--src/core/hle/service/audio/audctl.h50
-rw-r--r--src/core/hle/service/audio/audio.cpp4
-rw-r--r--src/core/hle/service/audio/audio_controller.cpp174
-rw-r--r--src/core/hle/service/audio/audio_controller.h58
-rw-r--r--src/core/hle/service/caps/caps_a.cpp11
-rw-r--r--src/core/hle/service/caps/caps_a.h3
-rw-r--r--src/core/hle/service/erpt/erpt.cpp12
-rw-r--r--src/core/hle/service/filesystem/fsp/fsp_srv.cpp9
-rw-r--r--src/core/hle/service/filesystem/fsp/fsp_srv.h1
-rw-r--r--src/core/hle/service/glue/time/manager.cpp4
-rw-r--r--src/core/hle/service/glue/time/manager.h1
-rw-r--r--src/core/hle/service/glue/time/static.cpp41
-rw-r--r--src/core/hle/service/glue/time/time_zone.cpp36
-rw-r--r--src/core/hle/service/ns/account_proxy_interface.cpp21
-rw-r--r--src/core/hle/service/ns/account_proxy_interface.h16
-rw-r--r--src/core/hle/service/ns/application_manager_interface.cpp519
-rw-r--r--src/core/hle/service/ns/application_manager_interface.h62
-rw-r--r--src/core/hle/service/ns/application_version_interface.cpp33
-rw-r--r--src/core/hle/service/ns/application_version_interface.h16
-rw-r--r--src/core/hle/service/ns/content_management_interface.cpp72
-rw-r--r--src/core/hle/service/ns/content_management_interface.h25
-rw-r--r--src/core/hle/service/ns/develop_interface.cpp38
-rw-r--r--src/core/hle/service/ns/develop_interface.h16
-rw-r--r--src/core/hle/service/ns/document_interface.cpp38
-rw-r--r--src/core/hle/service/ns/document_interface.h22
-rw-r--r--src/core/hle/service/ns/download_task_interface.cpp39
-rw-r--r--src/core/hle/service/ns/download_task_interface.h20
-rw-r--r--src/core/hle/service/ns/dynamic_rights_interface.cpp62
-rw-r--r--src/core/hle/service/ns/dynamic_rights_interface.h22
-rw-r--r--src/core/hle/service/ns/ecommerce_interface.cpp27
-rw-r--r--src/core/hle/service/ns/ecommerce_interface.h16
-rw-r--r--src/core/hle/service/ns/factory_reset_interface.cpp27
-rw-r--r--src/core/hle/service/ns/factory_reset_interface.h16
-rw-r--r--src/core/hle/service/ns/ns.cpp903
-rw-r--r--src/core/hle/service/ns/ns.h133
-rw-r--r--src/core/hle/service/ns/ns_results.h (renamed from src/core/hle/service/ns/errors.h)0
-rw-r--r--src/core/hle/service/ns/ns_types.h111
-rw-r--r--src/core/hle/service/ns/pdm_qry.cpp67
-rw-r--r--src/core/hle/service/ns/platform_service_manager.cpp (renamed from src/core/hle/service/ns/iplatform_service_manager.cpp)130
-rw-r--r--src/core/hle/service/ns/platform_service_manager.h (renamed from src/core/hle/service/ns/iplatform_service_manager.h)33
-rw-r--r--src/core/hle/service/ns/query_service.cpp57
-rw-r--r--src/core/hle/service/ns/query_service.h (renamed from src/core/hle/service/ns/pdm_qry.h)12
-rw-r--r--src/core/hle/service/ns/read_only_application_control_data_interface.cpp122
-rw-r--r--src/core/hle/service/ns/read_only_application_control_data_interface.h30
-rw-r--r--src/core/hle/service/ns/read_only_application_record_interface.cpp38
-rw-r--r--src/core/hle/service/ns/read_only_application_record_interface.h22
-rw-r--r--src/core/hle/service/ns/service_getter_interface.cpp120
-rw-r--r--src/core/hle/service/ns/service_getter_interface.h47
-rw-r--r--src/core/hle/service/ns/system_update_control.cpp44
-rw-r--r--src/core/hle/service/ns/system_update_control.h16
-rw-r--r--src/core/hle/service/ns/system_update_interface.cpp61
-rw-r--r--src/core/hle/service/ns/system_update_interface.h38
-rw-r--r--src/core/hle/service/ns/vulnerability_manager_interface.cpp31
-rw-r--r--src/core/hle/service/ns/vulnerability_manager_interface.h21
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp4
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.cpp3
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.h9
-rw-r--r--src/core/hle/service/nvdrv/nvdrv_interface.cpp10
-rw-r--r--src/core/hle/service/nvdrv/nvdrv_interface.h4
-rw-r--r--src/core/hle/service/nvnflinger/binder.h23
-rw-r--r--src/core/hle/service/nvnflinger/buffer_item_consumer.cpp2
-rw-r--r--src/core/hle/service/nvnflinger/buffer_item_consumer.h2
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp76
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_consumer.h10
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_producer.cpp32
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_producer.h8
-rw-r--r--src/core/hle/service/nvnflinger/consumer_base.cpp2
-rw-r--r--src/core/hle/service/nvnflinger/consumer_base.h4
-rw-r--r--src/core/hle/service/nvnflinger/display.h55
-rw-r--r--src/core/hle/service/nvnflinger/hardware_composer.cpp65
-rw-r--r--src/core/hle/service/nvnflinger/hardware_composer.h20
-rw-r--r--src/core/hle/service/nvnflinger/hos_binder_driver.cpp (renamed from src/core/hle/service/vi/hos_binder_driver.cpp)31
-rw-r--r--src/core/hle/service/nvnflinger/hos_binder_driver.h (renamed from src/core/hle/service/vi/hos_binder_driver.h)30
-rw-r--r--src/core/hle/service/nvnflinger/hos_binder_driver_server.cpp22
-rw-r--r--src/core/hle/service/nvnflinger/hos_binder_driver_server.h16
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.cpp333
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.h166
-rw-r--r--src/core/hle/service/nvnflinger/surface_flinger.cpp124
-rw-r--r--src/core/hle/service/nvnflinger/surface_flinger.h65
-rw-r--r--src/core/hle/service/psc/time/static.cpp33
-rw-r--r--src/core/hle/service/psc/time/steady_clock.cpp25
-rw-r--r--src/core/hle/service/psc/time/system_clock.cpp8
-rw-r--r--src/core/hle/service/psc/time/time_zone_service.cpp32
-rw-r--r--src/core/hle/service/server_manager.cpp8
-rw-r--r--src/core/hle/service/service.cpp136
-rw-r--r--src/core/hle/service/service.h21
-rw-r--r--src/core/hle/service/services.cpp136
-rw-r--r--src/core/hle/service/services.h22
-rw-r--r--src/core/hle/service/vi/application_display_service.cpp123
-rw-r--r--src/core/hle/service/vi/application_display_service.h34
-rw-r--r--src/core/hle/service/vi/application_root_service.cpp13
-rw-r--r--src/core/hle/service/vi/application_root_service.h12
-rw-r--r--src/core/hle/service/vi/conductor.cpp114
-rw-r--r--src/core/hle/service/vi/conductor.h57
-rw-r--r--src/core/hle/service/vi/container.cpp228
-rw-r--r--src/core/hle/service/vi/container.h92
-rw-r--r--src/core/hle/service/vi/display.h44
-rw-r--r--src/core/hle/service/vi/display/vi_display.cpp143
-rw-r--r--src/core/hle/service/vi/display/vi_display.h143
-rw-r--r--src/core/hle/service/vi/display_list.h83
-rw-r--r--src/core/hle/service/vi/layer.h79
-rw-r--r--src/core/hle/service/vi/layer/vi_layer.cpp18
-rw-r--r--src/core/hle/service/vi/layer/vi_layer.h118
-rw-r--r--src/core/hle/service/vi/layer_list.h69
-rw-r--r--src/core/hle/service/vi/manager_display_service.cpp46
-rw-r--r--src/core/hle/service/vi/manager_display_service.h21
-rw-r--r--src/core/hle/service/vi/manager_root_service.cpp14
-rw-r--r--src/core/hle/service/vi/manager_root_service.h14
-rw-r--r--src/core/hle/service/vi/service_creator.cpp5
-rw-r--r--src/core/hle/service/vi/service_creator.h9
-rw-r--r--src/core/hle/service/vi/shared_buffer_manager.cpp (renamed from src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp)136
-rw-r--r--src/core/hle/service/vi/shared_buffer_manager.h (renamed from src/core/hle/service/nvnflinger/fb_share_buffer_manager.h)46
-rw-r--r--src/core/hle/service/vi/system_display_service.cpp66
-rw-r--r--src/core/hle/service/vi/system_display_service.h20
-rw-r--r--src/core/hle/service/vi/system_root_service.cpp11
-rw-r--r--src/core/hle/service/vi/system_root_service.h12
-rw-r--r--src/core/hle/service/vi/vi.cpp21
-rw-r--r--src/core/hle/service/vi/vi.h10
-rw-r--r--src/core/hle/service/vi/vi_types.h10
-rw-r--r--src/core/hle/service/vi/vsync_manager.cpp26
-rw-r--r--src/core/hle/service/vi/vsync_manager.h29
-rw-r--r--src/core/loader/nca.cpp4
-rw-r--r--src/core/memory.cpp8
-rw-r--r--src/core/memory/cheat_engine.cpp4
-rw-r--r--src/core/memory/dmnt_cheat_vm.cpp4
-rw-r--r--src/frontend_common/config.cpp1
-rw-r--r--src/frontend_common/content_manager.h5
-rw-r--r--src/hid_core/frontend/emulated_controller.cpp66
-rw-r--r--src/hid_core/frontend/emulated_controller.h3
-rw-r--r--src/input_common/CMakeLists.txt10
-rw-r--r--src/input_common/drivers/android.cpp324
-rw-r--r--src/input_common/drivers/android.h124
-rw-r--r--src/input_common/helpers/joycon_driver.cpp4
-rw-r--r--src/input_common/main.cpp28
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_image.cpp47
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_instructions.h4
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h2
-rw-r--r--src/video_core/engines/maxwell_3d.cpp4
-rw-r--r--src/video_core/fence_manager.h4
-rw-r--r--src/video_core/gpu_thread.cpp4
-rw-r--r--src/video_core/host1x/ffmpeg/ffmpeg.cpp4
-rw-r--r--src/video_core/macro/macro_hle.cpp8
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp8
-rw-r--r--src/video_core/renderer_vulkan/present/layer.cpp4
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp4
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp8
-rw-r--r--src/video_core/vulkan_common/nsight_aftermath_tracker.cpp4
-rw-r--r--src/yuzu/configuration/qt_config.cpp1
-rw-r--r--src/yuzu/main.cpp171
-rw-r--r--src/yuzu/main.h1
-rw-r--r--src/yuzu/main.ui18
-rw-r--r--src/yuzu_cmd/sdl_config.cpp1
-rw-r--r--src/yuzu_cmd/yuzu.cpp4
343 files changed, 11631 insertions, 5613 deletions
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 7890b30ca..b037fc055 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -14,6 +14,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+ <uses-permission android:name="android.permission.VIBRATE" />
<application
android:name="org.yuzu.yuzu_emu.YuzuApplication"
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index 6ebb46af7..02a20dacf 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -3,24 +3,21 @@
package org.yuzu.yuzu_emu
-import android.app.Dialog
import android.content.DialogInterface
import android.net.Uri
-import android.os.Bundle
import android.text.Html
import android.text.method.LinkMovementMethod
import android.view.Surface
import android.view.View
import android.widget.TextView
import androidx.annotation.Keep
-import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.ref.WeakReference
import org.yuzu.yuzu_emu.activities.EmulationActivity
+import org.yuzu.yuzu_emu.fragments.CoreErrorDialogFragment
import org.yuzu.yuzu_emu.utils.DocumentsTree
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.Log
-import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
import org.yuzu.yuzu_emu.model.InstallResult
import org.yuzu.yuzu_emu.model.Patch
import org.yuzu.yuzu_emu.model.GameVerificationResult
@@ -30,34 +27,6 @@ import org.yuzu.yuzu_emu.model.GameVerificationResult
* with the native side of the Yuzu code.
*/
object NativeLibrary {
- /**
- * Default controller id for each device
- */
- const val Player1Device = 0
- const val Player2Device = 1
- const val Player3Device = 2
- const val Player4Device = 3
- const val Player5Device = 4
- const val Player6Device = 5
- const val Player7Device = 6
- const val Player8Device = 7
- const val ConsoleDevice = 8
-
- /**
- * Controller type for each device
- */
- const val ProController = 3
- const val Handheld = 4
- const val JoyconDual = 5
- const val JoyconLeft = 6
- const val JoyconRight = 7
- const val GameCube = 8
- const val Pokeball = 9
- const val NES = 10
- const val SNES = 11
- const val N64 = 12
- const val SegaGenesis = 13
-
@JvmField
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
@@ -127,112 +96,6 @@ object NativeLibrary {
FileUtil.getFilename(Uri.parse(path))
}
- /**
- * Returns true if pro controller isn't available and handheld is
- */
- external fun isHandheldOnly(): Boolean
-
- /**
- * Changes controller type for a specific device.
- *
- * @param Device The input descriptor of the gamepad.
- * @param Type The NpadStyleIndex of the gamepad.
- */
- external fun setDeviceType(Device: Int, Type: Int): Boolean
-
- /**
- * Handles event when a gamepad is connected.
- *
- * @param Device The input descriptor of the gamepad.
- */
- external fun onGamePadConnectEvent(Device: Int): Boolean
-
- /**
- * Handles event when a gamepad is disconnected.
- *
- * @param Device The input descriptor of the gamepad.
- */
- external fun onGamePadDisconnectEvent(Device: Int): Boolean
-
- /**
- * Handles button press events for a gamepad.
- *
- * @param Device The input descriptor of the gamepad.
- * @param Button Key code identifying which button was pressed.
- * @param Action Mask identifying which action is happening (button pressed down, or button released).
- * @return If we handled the button press.
- */
- external fun onGamePadButtonEvent(Device: Int, Button: Int, Action: Int): Boolean
-
- /**
- * Handles joystick movement events.
- *
- * @param Device The device ID of the gamepad.
- * @param Axis The axis ID
- * @param x_axis The value of the x-axis represented by the given ID.
- * @param y_axis The value of the y-axis represented by the given ID.
- */
- external fun onGamePadJoystickEvent(
- Device: Int,
- Axis: Int,
- x_axis: Float,
- y_axis: Float
- ): Boolean
-
- /**
- * Handles motion events.
- *
- * @param delta_timestamp The finger id corresponding to this event
- * @param gyro_x,gyro_y,gyro_z The value of the accelerometer sensor.
- * @param accel_x,accel_y,accel_z The value of the y-axis
- */
- external fun onGamePadMotionEvent(
- Device: Int,
- delta_timestamp: Long,
- gyro_x: Float,
- gyro_y: Float,
- gyro_z: Float,
- accel_x: Float,
- accel_y: Float,
- accel_z: Float
- ): Boolean
-
- /**
- * Signals and load a nfc tag
- *
- * @param data Byte array containing all the data from a nfc tag
- */
- external fun onReadNfcTag(data: ByteArray?): Boolean
-
- /**
- * Removes current loaded nfc tag
- */
- external fun onRemoveNfcTag(): Boolean
-
- /**
- * Handles touch press events.
- *
- * @param finger_id The finger id corresponding to this event
- * @param x_axis The value of the x-axis.
- * @param y_axis The value of the y-axis.
- */
- external fun onTouchPressed(finger_id: Int, x_axis: Float, y_axis: Float)
-
- /**
- * Handles touch movement.
- *
- * @param x_axis The value of the instantaneous x-axis.
- * @param y_axis The value of the instantaneous y-axis.
- */
- external fun onTouchMoved(finger_id: Int, x_axis: Float, y_axis: Float)
-
- /**
- * Handles touch release events.
- *
- * @param finger_id The finger id corresponding to this event
- */
- external fun onTouchReleased(finger_id: Int)
-
external fun setAppDirectory(directory: String)
/**
@@ -318,46 +181,13 @@ object NativeLibrary {
ErrorUnknown
}
- private var coreErrorAlertResult = false
- private val coreErrorAlertLock = Object()
-
- class CoreErrorDialogFragment : DialogFragment() {
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val title = requireArguments().serializable<String>("title")
- val message = requireArguments().serializable<String>("message")
-
- return MaterialAlertDialogBuilder(requireActivity())
- .setTitle(title)
- .setMessage(message)
- .setPositiveButton(R.string.continue_button, null)
- .setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int ->
- coreErrorAlertResult = false
- synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
- }
- .create()
- }
-
- override fun onDismiss(dialog: DialogInterface) {
- coreErrorAlertResult = true
- synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
- }
-
- companion object {
- fun newInstance(title: String?, message: String?): CoreErrorDialogFragment {
- val frag = CoreErrorDialogFragment()
- val args = Bundle()
- args.putString("title", title)
- args.putString("message", message)
- frag.arguments = args
- return frag
- }
- }
- }
+ var coreErrorAlertResult = false
+ val coreErrorAlertLock = Object()
private fun onCoreErrorImpl(title: String, message: String) {
val emulationActivity = sEmulationActivity.get()
if (emulationActivity == null) {
- error("[NativeLibrary] EmulationActivity not present")
+ Log.error("[NativeLibrary] EmulationActivity not present")
return
}
@@ -373,7 +203,7 @@ object NativeLibrary {
fun onCoreError(error: CoreError?, details: String): Boolean {
val emulationActivity = sEmulationActivity.get()
if (emulationActivity == null) {
- error("[NativeLibrary] EmulationActivity not present")
+ Log.error("[NativeLibrary] EmulationActivity not present")
return false
}
@@ -404,7 +234,7 @@ object NativeLibrary {
}
// Show the AlertDialog on the main thread.
- emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message) })
+ emulationActivity.runOnUiThread { onCoreErrorImpl(title, message) }
// Wait for the lock to notify that it is complete.
synchronized(coreErrorAlertLock) { coreErrorAlertLock.wait() }
@@ -629,46 +459,4 @@ object NativeLibrary {
* Checks if all necessary keys are present for decryption
*/
external fun areKeysPresent(): Boolean
-
- /**
- * Button type for use in onTouchEvent
- */
- object ButtonType {
- const val BUTTON_A = 0
- const val BUTTON_B = 1
- const val BUTTON_X = 2
- const val BUTTON_Y = 3
- const val STICK_L = 4
- const val STICK_R = 5
- const val TRIGGER_L = 6
- const val TRIGGER_R = 7
- const val TRIGGER_ZL = 8
- const val TRIGGER_ZR = 9
- const val BUTTON_PLUS = 10
- const val BUTTON_MINUS = 11
- const val DPAD_LEFT = 12
- const val DPAD_UP = 13
- const val DPAD_RIGHT = 14
- const val DPAD_DOWN = 15
- const val BUTTON_SL = 16
- const val BUTTON_SR = 17
- const val BUTTON_HOME = 18
- const val BUTTON_CAPTURE = 19
- }
-
- /**
- * Stick type for use in onTouchEvent
- */
- object StickType {
- const val STICK_L = 0
- const val STICK_R = 1
- }
-
- /**
- * Button states
- */
- object ButtonState {
- const val RELEASED = 0
- const val PRESSED = 1
- }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
index 76778c10a..72943f33e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
@@ -7,6 +7,7 @@ import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
+import org.yuzu.yuzu_emu.features.input.NativeInput
import java.io.File
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.DocumentsTree
@@ -37,6 +38,7 @@ class YuzuApplication : Application() {
documentsTree = DocumentsTree()
DirectoryInitialization.start()
GpuDriverHelper.initializeDriverParameters()
+ NativeInput.reloadInputDevices()
NativeLibrary.logDeviceInfo()
Log.logDeviceInfo()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 7a8d03610..c962558a7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -39,6 +39,7 @@ import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
+import org.yuzu.yuzu_emu.features.input.NativeInput
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
@@ -47,7 +48,9 @@ import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.MemoryUtil
+import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.NfcReader
+import org.yuzu.yuzu_emu.utils.ParamPackage
import org.yuzu.yuzu_emu.utils.ThemeHelper
import java.text.NumberFormat
import kotlin.math.roundToInt
@@ -63,8 +66,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
private var motionTimestamp: Long = 0
private var flipMotionOrientation: Boolean = false
- private var controllerIds = InputHandler.getGameControllerIds()
-
private val actionPause = "ACTION_EMULATOR_PAUSE"
private val actionPlay = "ACTION_EMULATOR_PLAY"
private val actionMute = "ACTION_EMULATOR_MUTE"
@@ -78,6 +79,33 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
super.onCreate(savedInstanceState)
+ InputHandler.updateControllerData()
+ val players = NativeConfig.getInputSettings(true)
+ var hasConfiguredControllers = false
+ players.forEach {
+ if (it.hasMapping()) {
+ hasConfiguredControllers = true
+ }
+ }
+ if (!hasConfiguredControllers && InputHandler.androidControllers.isNotEmpty()) {
+ var params: ParamPackage? = null
+ for (controller in InputHandler.registeredControllers) {
+ if (controller.get("port", -1) == 0) {
+ params = controller
+ break
+ }
+ }
+
+ if (params != null) {
+ NativeInput.updateMappingsWithDefault(
+ 0,
+ params,
+ params.get("display", getString(R.string.unknown))
+ )
+ NativeConfig.saveGlobalConfig()
+ }
+ }
+
binding = ActivityEmulationBinding.inflate(layoutInflater)
setContentView(binding.root)
@@ -95,8 +123,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
nfcReader = NfcReader(this)
nfcReader.initialize()
- InputHandler.initialize()
-
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.totalMemory)) {
@@ -147,7 +173,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
super.onResume()
nfcReader.startScanning()
startMotionSensorListener()
- InputHandler.updateControllerIds()
+ InputHandler.updateControllerData()
buildPictureInPictureParams()
}
@@ -172,6 +198,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
super.onNewIntent(intent)
setIntent(intent)
nfcReader.onNewIntent(intent)
+ InputHandler.updateControllerData()
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
@@ -244,8 +271,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
}
val deltaTimestamp = (event.timestamp - motionTimestamp) / 1000
motionTimestamp = event.timestamp
- NativeLibrary.onGamePadMotionEvent(
- NativeLibrary.Player1Device,
+ NativeInput.onDeviceMotionEvent(
+ NativeInput.Player1Device,
deltaTimestamp,
gyro[0],
gyro[1],
@@ -254,8 +281,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
accel[1],
accel[2]
)
- NativeLibrary.onGamePadMotionEvent(
- NativeLibrary.ConsoleDevice,
+ NativeInput.onDeviceMotionEvent(
+ NativeInput.ConsoleDevice,
deltaTimestamp,
gyro[0],
gyro[1],
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
index f218c76ef..50663ad91 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
@@ -3,15 +3,15 @@
package org.yuzu.yuzu_emu.adapters
-import android.text.TextUtils
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.model.Driver
import org.yuzu.yuzu_emu.model.DriverViewModel
+import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class DriverAdapter(private val driverViewModel: DriverViewModel) :
@@ -44,25 +44,15 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
}
// Delay marquee by 3s
- title.postDelayed(
- {
- title.isSelected = true
- title.ellipsize = TextUtils.TruncateAt.MARQUEE
- version.isSelected = true
- version.ellipsize = TextUtils.TruncateAt.MARQUEE
- description.isSelected = true
- description.ellipsize = TextUtils.TruncateAt.MARQUEE
- },
- 3000
- )
+ title.marquee()
+ version.marquee()
+ description.marquee()
title.text = model.title
version.text = model.version
description.text = model.description
- if (model.title != binding.root.context.getString(R.string.system_gpu_driver)) {
- buttonDelete.visibility = View.VISIBLE
- } else {
- buttonDelete.visibility = View.GONE
- }
+ buttonDelete.setVisible(
+ model.title != binding.root.context.getString(R.string.system_gpu_driver)
+ )
}
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
index 3d8f0bda8..5cbd15d2a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
@@ -4,7 +4,6 @@
package org.yuzu.yuzu_emu.adapters
import android.net.Uri
-import android.text.TextUtils
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity
@@ -12,6 +11,7 @@ import org.yuzu.yuzu_emu.databinding.CardFolderBinding
import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
+import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
@@ -29,13 +29,7 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
override fun bind(model: GameDir) {
binding.apply {
path.text = Uri.parse(model.uriString).path
- path.postDelayed(
- {
- path.isSelected = true
- path.ellipsize = TextUtils.TruncateAt.MARQUEE
- },
- 3000
- )
+ path.marquee()
buttonEdit.setOnClickListener {
GameFolderPropertiesDialogFragment.newInstance(model)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
index 85c8249e6..b1f247ac3 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
@@ -4,7 +4,6 @@
package org.yuzu.yuzu_emu.adapters
import android.net.Uri
-import android.text.TextUtils
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
@@ -27,6 +26,7 @@ import org.yuzu.yuzu_emu.databinding.CardGameBinding
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.utils.GameIconUtils
+import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class GameAdapter(private val activity: AppCompatActivity) :
@@ -44,14 +44,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
binding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ")
- binding.textGameTitle.postDelayed(
- {
- binding.textGameTitle.ellipsize = TextUtils.TruncateAt.MARQUEE
- binding.textGameTitle.isSelected = true
- },
- 3000
- )
-
+ binding.textGameTitle.marquee()
binding.cardGame.setOnClickListener { onClick(model) }
binding.cardGame.setOnLongClickListener { onLongClick(model) }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt
index 0046d5314..7366e2c77 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt
@@ -3,21 +3,18 @@
package org.yuzu.yuzu_emu.adapters
-import android.text.TextUtils
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
import androidx.core.content.res.ResourcesCompat
-import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
import org.yuzu.yuzu_emu.model.GameProperty
import org.yuzu.yuzu_emu.model.InstallableProperty
import org.yuzu.yuzu_emu.model.SubmenuProperty
+import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
+import org.yuzu.yuzu_emu.utils.collect
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class GamePropertiesAdapter(
@@ -76,23 +73,15 @@ class GamePropertiesAdapter(
)
)
- binding.details.postDelayed({
- binding.details.isSelected = true
- binding.details.ellipsize = TextUtils.TruncateAt.MARQUEE
- }, 3000)
-
+ binding.details.marquee()
if (submenuProperty.details != null) {
- binding.details.visibility = View.VISIBLE
+ binding.details.setVisible(true)
binding.details.text = submenuProperty.details.invoke()
} else if (submenuProperty.detailsFlow != null) {
- binding.details.visibility = View.VISIBLE
- viewLifecycle.lifecycleScope.launch {
- viewLifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
- submenuProperty.detailsFlow.collect { binding.details.text = it }
- }
- }
+ binding.details.setVisible(true)
+ submenuProperty.detailsFlow.collect(viewLifecycle) { binding.details.text = it }
} else {
- binding.details.visibility = View.GONE
+ binding.details.setVisible(false)
}
}
}
@@ -112,14 +101,10 @@ class GamePropertiesAdapter(
)
)
- if (installableProperty.install != null) {
- binding.buttonInstall.visibility = View.VISIBLE
- binding.buttonInstall.setOnClickListener { installableProperty.install.invoke() }
- }
- if (installableProperty.export != null) {
- binding.buttonExport.visibility = View.VISIBLE
- binding.buttonExport.setOnClickListener { installableProperty.export.invoke() }
- }
+ binding.buttonInstall.setVisible(installableProperty.install != null)
+ binding.buttonInstall.setOnClickListener { installableProperty.install?.invoke() }
+ binding.buttonExport.setVisible(installableProperty.export != null)
+ binding.buttonExport.setOnClickListener { installableProperty.export?.invoke() }
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
index b512845d5..0bd196673 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
@@ -3,22 +3,19 @@
package org.yuzu.yuzu_emu.adapters
-import android.text.TextUtils
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
-import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.HomeSetting
+import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
+import org.yuzu.yuzu_emu.utils.collect
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class HomeSettingAdapter(
@@ -59,18 +56,8 @@ class HomeSettingAdapter(
binding.optionIcon.alpha = 0.5f
}
- viewLifecycle.lifecycleScope.launch {
- viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
- model.details.collect { updateOptionDetails(it) }
- }
- }
- binding.optionDetail.postDelayed(
- {
- binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
- binding.optionDetail.isSelected = true
- },
- 3000
- )
+ model.details.collect(viewLifecycle) { updateOptionDetails(it) }
+ binding.optionDetail.marquee()
binding.root.setOnClickListener { onClick(model) }
}
@@ -90,7 +77,7 @@ class HomeSettingAdapter(
private fun updateOptionDetails(detailString: String) {
if (detailString.isNotEmpty()) {
binding.optionDetail.text = detailString
- binding.optionDetail.visibility = View.VISIBLE
+ binding.optionDetail.setVisible(true)
}
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
index 4218c4e52..1ba75fa2f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
@@ -4,10 +4,10 @@
package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
import org.yuzu.yuzu_emu.model.Installable
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class InstallableAdapter(installables: List<Installable>) :
@@ -26,14 +26,10 @@ class InstallableAdapter(installables: List<Installable>) :
binding.title.setText(model.titleId)
binding.description.setText(model.descriptionId)
- if (model.install != null) {
- binding.buttonInstall.visibility = View.VISIBLE
- binding.buttonInstall.setOnClickListener { model.install.invoke() }
- }
- if (model.export != null) {
- binding.buttonExport.visibility = View.VISIBLE
- binding.buttonExport.setOnClickListener { model.export.invoke() }
- }
+ binding.buttonInstall.setVisible(model.install != null)
+ binding.buttonInstall.setOnClickListener { model.install?.invoke() }
+ binding.buttonExport.setVisible(model.export != null)
+ binding.buttonExport.setOnClickListener { model.export?.invoke() }
}
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
index 38bb1f96f..1379968f9 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
@@ -4,12 +4,12 @@
package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
import org.yuzu.yuzu_emu.model.License
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<License>) :
@@ -25,7 +25,7 @@ class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<Lic
binding.apply {
textSettingName.text = root.context.getString(model.titleId)
textSettingDescription.text = root.context.getString(model.descriptionId)
- textSettingValue.visibility = View.GONE
+ textSettingValue.setVisible(false)
root.setOnClickListener { onClick(model) }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
index 02118e1a8..a5f610b31 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
@@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.adapters
import android.text.Html
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
@@ -17,6 +16,7 @@ import org.yuzu.yuzu_emu.model.SetupCallback
import org.yuzu.yuzu_emu.model.SetupPage
import org.yuzu.yuzu_emu.model.StepState
import org.yuzu.yuzu_emu.utils.ViewUtils
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
@@ -30,8 +30,8 @@ class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
AbstractViewHolder<SetupPage>(binding), SetupCallback {
override fun bind(model: SetupPage) {
if (model.stepCompleted.invoke() == StepState.COMPLETE) {
- binding.buttonAction.visibility = View.INVISIBLE
- binding.textConfirmation.visibility = View.VISIBLE
+ binding.buttonAction.setVisible(visible = false, gone = false)
+ binding.textConfirmation.setVisible(true)
}
binding.icon.setImageDrawable(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/NativeInput.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/NativeInput.kt
new file mode 100644
index 000000000..15d776311
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/NativeInput.kt
@@ -0,0 +1,416 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.input
+
+import org.yuzu.yuzu_emu.features.input.model.NativeButton
+import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
+import org.yuzu.yuzu_emu.features.input.model.InputType
+import org.yuzu.yuzu_emu.features.input.model.ButtonName
+import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex
+import org.yuzu.yuzu_emu.utils.NativeConfig
+import org.yuzu.yuzu_emu.utils.ParamPackage
+import android.view.InputDevice
+
+object NativeInput {
+ /**
+ * Default controller id for each device
+ */
+ const val Player1Device = 0
+ const val Player2Device = 1
+ const val Player3Device = 2
+ const val Player4Device = 3
+ const val Player5Device = 4
+ const val Player6Device = 5
+ const val Player7Device = 6
+ const val Player8Device = 7
+ const val ConsoleDevice = 8
+
+ /**
+ * Button states
+ */
+ object ButtonState {
+ const val RELEASED = 0
+ const val PRESSED = 1
+ }
+
+ /**
+ * Returns true if pro controller isn't available and handheld is.
+ * Intended to check where the input overlay should direct its inputs.
+ */
+ external fun isHandheldOnly(): Boolean
+
+ /**
+ * Handles button press events for a gamepad.
+ * @param guid 32 character hexadecimal string consisting of the controller's PID+VID.
+ * @param port Port determined by controller connection order.
+ * @param buttonId The Android Keycode corresponding to this event.
+ * @param action Mask identifying which action is happening (button pressed down, or button released).
+ */
+ external fun onGamePadButtonEvent(
+ guid: String,
+ port: Int,
+ buttonId: Int,
+ action: Int
+ )
+
+ /**
+ * Handles axis movement events.
+ * @param guid 32 character hexadecimal string consisting of the controller's PID+VID.
+ * @param port Port determined by controller connection order.
+ * @param axis The axis ID.
+ * @param value Value along the given axis.
+ */
+ external fun onGamePadAxisEvent(guid: String, port: Int, axis: Int, value: Float)
+
+ /**
+ * Handles motion events.
+ * @param guid 32 character hexadecimal string consisting of the controller's PID+VID.
+ * @param port Port determined by controller connection order.
+ * @param deltaTimestamp The finger id corresponding to this event.
+ * @param xGyro The value of the x-axis for the gyroscope.
+ * @param yGyro The value of the y-axis for the gyroscope.
+ * @param zGyro The value of the z-axis for the gyroscope.
+ * @param xAccel The value of the x-axis for the accelerometer.
+ * @param yAccel The value of the y-axis for the accelerometer.
+ * @param zAccel The value of the z-axis for the accelerometer.
+ */
+ external fun onGamePadMotionEvent(
+ guid: String,
+ port: Int,
+ deltaTimestamp: Long,
+ xGyro: Float,
+ yGyro: Float,
+ zGyro: Float,
+ xAccel: Float,
+ yAccel: Float,
+ zAccel: Float
+ )
+
+ /**
+ * Signals and load a nfc tag
+ * @param data Byte array containing all the data from a nfc tag.
+ */
+ external fun onReadNfcTag(data: ByteArray?)
+
+ /**
+ * Removes current loaded nfc tag.
+ */
+ external fun onRemoveNfcTag()
+
+ /**
+ * Handles touch press events.
+ * @param fingerId The finger id corresponding to this event.
+ * @param xAxis The value of the x-axis on the touchscreen.
+ * @param yAxis The value of the y-axis on the touchscreen.
+ */
+ external fun onTouchPressed(fingerId: Int, xAxis: Float, yAxis: Float)
+
+ /**
+ * Handles touch movement.
+ * @param fingerId The finger id corresponding to this event.
+ * @param xAxis The value of the x-axis on the touchscreen.
+ * @param yAxis The value of the y-axis on the touchscreen.
+ */
+ external fun onTouchMoved(fingerId: Int, xAxis: Float, yAxis: Float)
+
+ /**
+ * Handles touch release events.
+ * @param fingerId The finger id corresponding to this event
+ */
+ external fun onTouchReleased(fingerId: Int)
+
+ /**
+ * Sends a button input to the global virtual controllers.
+ * @param port Port determined by controller connection order.
+ * @param button The [NativeButton] corresponding to this event.
+ * @param action Mask identifying which action is happening (button pressed down, or button released).
+ */
+ fun onOverlayButtonEvent(port: Int, button: NativeButton, action: Int) =
+ onOverlayButtonEventImpl(port, button.int, action)
+
+ private external fun onOverlayButtonEventImpl(port: Int, buttonId: Int, action: Int)
+
+ /**
+ * Sends a joystick input to the global virtual controllers.
+ * @param port Port determined by controller connection order.
+ * @param stick The [NativeAnalog] corresponding to this event.
+ * @param xAxis Value along the X axis.
+ * @param yAxis Value along the Y axis.
+ */
+ fun onOverlayJoystickEvent(port: Int, stick: NativeAnalog, xAxis: Float, yAxis: Float) =
+ onOverlayJoystickEventImpl(port, stick.int, xAxis, yAxis)
+
+ private external fun onOverlayJoystickEventImpl(
+ port: Int,
+ stickId: Int,
+ xAxis: Float,
+ yAxis: Float
+ )
+
+ /**
+ * Handles motion events for the global virtual controllers.
+ * @param port Port determined by controller connection order
+ * @param deltaTimestamp The finger id corresponding to this event.
+ * @param xGyro The value of the x-axis for the gyroscope.
+ * @param yGyro The value of the y-axis for the gyroscope.
+ * @param zGyro The value of the z-axis for the gyroscope.
+ * @param xAccel The value of the x-axis for the accelerometer.
+ * @param yAccel The value of the y-axis for the accelerometer.
+ * @param zAccel The value of the z-axis for the accelerometer.
+ */
+ external fun onDeviceMotionEvent(
+ port: Int,
+ deltaTimestamp: Long,
+ xGyro: Float,
+ yGyro: Float,
+ zGyro: Float,
+ xAccel: Float,
+ yAccel: Float,
+ zAccel: Float
+ )
+
+ /**
+ * Reloads all input devices from the currently loaded Settings::values.players into HID Core
+ */
+ external fun reloadInputDevices()
+
+ /**
+ * Registers a controller to be used with mapping
+ * @param device An [InputDevice] or the input overlay wrapped with [YuzuInputDevice]
+ */
+ external fun registerController(device: YuzuInputDevice)
+
+ /**
+ * Gets the names of input devices that have been registered with the input subsystem via [registerController]
+ */
+ external fun getInputDevices(): Array<String>
+
+ /**
+ * Reads all input profiles from disk. Must be called before creating a profile picker.
+ */
+ external fun loadInputProfiles()
+
+ /**
+ * Gets the names of each available input profile.
+ */
+ external fun getInputProfileNames(): Array<String>
+
+ /**
+ * Checks if the user-provided name for an input profile is valid.
+ * @param name User-provided name for an input profile.
+ * @return Whether [name] is valid or not.
+ */
+ external fun isProfileNameValid(name: String): Boolean
+
+ /**
+ * Creates a new input profile.
+ * @param name The new profile's name.
+ * @param playerIndex Index of the player that's currently being edited. Used to write the profile
+ * name to this player's config.
+ * @return Whether creating the profile was successful or not.
+ */
+ external fun createProfile(name: String, playerIndex: Int): Boolean
+
+ /**
+ * Deletes an input profile.
+ * @param name Name of the profile to delete.
+ * @param playerIndex Index of the player that's currently being edited. Used to remove the profile
+ * name from this player's config if they have it loaded.
+ * @return Whether deleting this profile was successful or not.
+ */
+ external fun deleteProfile(name: String, playerIndex: Int): Boolean
+
+ /**
+ * Loads an input profile.
+ * @param name Name of the input profile to load.
+ * @param playerIndex Index of the player that will have this profile loaded.
+ * @return Whether loading this profile was successful or not.
+ */
+ external fun loadProfile(name: String, playerIndex: Int): Boolean
+
+ /**
+ * Saves an input profile.
+ * @param name Name of the profile to save.
+ * @param playerIndex Index of the player that's currently being edited. Used to write the profile
+ * name to this player's config.
+ * @return Whether saving the profile was successful or not.
+ */
+ external fun saveProfile(name: String, playerIndex: Int): Boolean
+
+ /**
+ * Intended to be used immediately before a call to [NativeConfig.saveControlPlayerValues]
+ * Must be used while per-game config is loaded.
+ */
+ external fun loadPerGameConfiguration(
+ playerIndex: Int,
+ selectedIndex: Int,
+ selectedProfileName: String
+ )
+
+ /**
+ * Tells the input subsystem to start listening for inputs to map.
+ * @param type Type of input to map as shown by the int property in each [InputType].
+ */
+ external fun beginMapping(type: Int)
+
+ /**
+ * Gets an input's [ParamPackage] as a serialized string. Used for input verification before mapping.
+ * Must be run after [beginMapping] and before [stopMapping].
+ */
+ external fun getNextInput(): String
+
+ /**
+ * Tells the input subsystem to stop listening for inputs to map.
+ */
+ external fun stopMapping()
+
+ /**
+ * Updates a controller's mappings with auto-mapping params.
+ * @param playerIndex Index of the player to auto-map.
+ * @param deviceParams [ParamPackage] representing the device to auto-map as received
+ * from [getInputDevices].
+ * @param displayName Name of the device to auto-map as received from the "display" param in [deviceParams].
+ * Intended to be a way to provide a default name for a controller if the "display" param is empty.
+ */
+ fun updateMappingsWithDefault(
+ playerIndex: Int,
+ deviceParams: ParamPackage,
+ displayName: String
+ ) = updateMappingsWithDefaultImpl(playerIndex, deviceParams.serialize(), displayName)
+
+ private external fun updateMappingsWithDefaultImpl(
+ playerIndex: Int,
+ deviceParams: String,
+ displayName: String
+ )
+
+ /**
+ * Gets the params for a specific button.
+ * @param playerIndex Index of the player to get params from.
+ * @param button The [NativeButton] to get params for.
+ * @return A [ParamPackage] representing a player's specific button.
+ */
+ fun getButtonParam(playerIndex: Int, button: NativeButton): ParamPackage =
+ ParamPackage(getButtonParamImpl(playerIndex, button.int))
+
+ private external fun getButtonParamImpl(playerIndex: Int, buttonId: Int): String
+
+ /**
+ * Sets the params for a specific button.
+ * @param playerIndex Index of the player to set params for.
+ * @param button The [NativeButton] to set params for.
+ * @param param A [ParamPackage] to set.
+ */
+ fun setButtonParam(playerIndex: Int, button: NativeButton, param: ParamPackage) =
+ setButtonParamImpl(playerIndex, button.int, param.serialize())
+
+ private external fun setButtonParamImpl(playerIndex: Int, buttonId: Int, param: String)
+
+ /**
+ * Gets the params for a specific stick.
+ * @param playerIndex Index of the player to get params from.
+ * @param stick The [NativeAnalog] to get params for.
+ * @return A [ParamPackage] representing a player's specific stick.
+ */
+ fun getStickParam(playerIndex: Int, stick: NativeAnalog): ParamPackage =
+ ParamPackage(getStickParamImpl(playerIndex, stick.int))
+
+ private external fun getStickParamImpl(playerIndex: Int, stickId: Int): String
+
+ /**
+ * Sets the params for a specific stick.
+ * @param playerIndex Index of the player to set params for.
+ * @param stick The [NativeAnalog] to set params for.
+ * @param param A [ParamPackage] to set.
+ */
+ fun setStickParam(playerIndex: Int, stick: NativeAnalog, param: ParamPackage) =
+ setStickParamImpl(playerIndex, stick.int, param.serialize())
+
+ private external fun setStickParamImpl(playerIndex: Int, stickId: Int, param: String)
+
+ /**
+ * Gets the int representation of a [ButtonName]. Tells you what to show as the mapped input for
+ * a button/analog/other.
+ * @param param A [ParamPackage] that represents a specific button's params.
+ * @return The [ButtonName] for [param].
+ */
+ fun getButtonName(param: ParamPackage): ButtonName =
+ ButtonName.from(getButtonNameImpl(param.serialize()))
+
+ private external fun getButtonNameImpl(param: String): Int
+
+ /**
+ * Gets each supported [NpadStyleIndex] for a given player.
+ * @param playerIndex Index of the player to get supported indexes for.
+ * @return List of each supported [NpadStyleIndex].
+ */
+ fun getSupportedStyleTags(playerIndex: Int): List<NpadStyleIndex> =
+ getSupportedStyleTagsImpl(playerIndex).map { NpadStyleIndex.from(it) }
+
+ private external fun getSupportedStyleTagsImpl(playerIndex: Int): IntArray
+
+ /**
+ * Gets the [NpadStyleIndex] for a given player.
+ * @param playerIndex Index of the player to get an [NpadStyleIndex] from.
+ * @return The [NpadStyleIndex] for a given player.
+ */
+ fun getStyleIndex(playerIndex: Int): NpadStyleIndex =
+ NpadStyleIndex.from(getStyleIndexImpl(playerIndex))
+
+ private external fun getStyleIndexImpl(playerIndex: Int): Int
+
+ /**
+ * Sets the [NpadStyleIndex] for a given player.
+ * @param playerIndex Index of the player to change.
+ * @param style The new style to set.
+ */
+ fun setStyleIndex(playerIndex: Int, style: NpadStyleIndex) =
+ setStyleIndexImpl(playerIndex, style.int)
+
+ private external fun setStyleIndexImpl(playerIndex: Int, styleIndex: Int)
+
+ /**
+ * Checks if a device is a controller.
+ * @param params [ParamPackage] for an input device retrieved from [getInputDevices]
+ * @return Whether the device is a controller or not.
+ */
+ fun isController(params: ParamPackage): Boolean = isControllerImpl(params.serialize())
+
+ private external fun isControllerImpl(params: String): Boolean
+
+ /**
+ * Checks if a controller is connected
+ * @param playerIndex Index of the player to check.
+ * @return Whether the player is connected or not.
+ */
+ external fun getIsConnected(playerIndex: Int): Boolean
+
+ /**
+ * Connects/disconnects a controller and ensures that connection order stays in-tact.
+ * @param playerIndex Index of the player to connect/disconnect.
+ * @param connected Whether to connect or disconnect this controller.
+ */
+ fun connectControllers(playerIndex: Int, connected: Boolean = true) {
+ val connectedControllers = mutableListOf<Boolean>().apply {
+ if (connected) {
+ for (i in 0 until 8) {
+ add(i <= playerIndex)
+ }
+ } else {
+ for (i in 0 until 8) {
+ add(i < playerIndex)
+ }
+ }
+ }
+ connectControllersImpl(connectedControllers.toBooleanArray())
+ }
+
+ private external fun connectControllersImpl(connected: BooleanArray)
+
+ /**
+ * Resets all of the button and analog mappings for a player.
+ * @param playerIndex Index of the player that will have its mappings reset.
+ */
+ external fun resetControllerMappings(playerIndex: Int)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/YuzuInputDevice.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/YuzuInputDevice.kt
new file mode 100644
index 000000000..15cc38c7f
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/YuzuInputDevice.kt
@@ -0,0 +1,93 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.input
+
+import android.view.InputDevice
+import androidx.annotation.Keep
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.utils.InputHandler.getGUID
+
+@Keep
+interface YuzuInputDevice {
+ fun getName(): String
+
+ fun getGUID(): String
+
+ fun getPort(): Int
+
+ fun getSupportsVibration(): Boolean
+
+ fun vibrate(intensity: Float)
+
+ fun getAxes(): Array<Int> = arrayOf()
+ fun hasKeys(keys: IntArray): BooleanArray = BooleanArray(0)
+}
+
+class YuzuPhysicalDevice(
+ private val device: InputDevice,
+ private val port: Int,
+ useSystemVibrator: Boolean
+) : YuzuInputDevice {
+ private val vibrator = if (useSystemVibrator) {
+ YuzuVibrator.getSystemVibrator()
+ } else {
+ YuzuVibrator.getControllerVibrator(device)
+ }
+
+ override fun getName(): String {
+ return device.name
+ }
+
+ override fun getGUID(): String {
+ return device.getGUID()
+ }
+
+ override fun getPort(): Int {
+ return port
+ }
+
+ override fun getSupportsVibration(): Boolean {
+ return vibrator.supportsVibration()
+ }
+
+ override fun vibrate(intensity: Float) {
+ vibrator.vibrate(intensity)
+ }
+
+ override fun getAxes(): Array<Int> = device.motionRanges.map { it.axis }.toTypedArray()
+ override fun hasKeys(keys: IntArray): BooleanArray = device.hasKeys(*keys)
+}
+
+class YuzuInputOverlayDevice(
+ private val vibration: Boolean,
+ private val port: Int
+) : YuzuInputDevice {
+ private val vibrator = YuzuVibrator.getSystemVibrator()
+
+ override fun getName(): String {
+ return YuzuApplication.appContext.getString(R.string.input_overlay)
+ }
+
+ override fun getGUID(): String {
+ return "00000000000000000000000000000000"
+ }
+
+ override fun getPort(): Int {
+ return port
+ }
+
+ override fun getSupportsVibration(): Boolean {
+ if (vibration) {
+ return vibrator.supportsVibration()
+ }
+ return false
+ }
+
+ override fun vibrate(intensity: Float) {
+ if (vibration) {
+ vibrator.vibrate(intensity)
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/YuzuVibrator.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/YuzuVibrator.kt
new file mode 100644
index 000000000..aac49ecae
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/YuzuVibrator.kt
@@ -0,0 +1,76 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.input
+
+import android.content.Context
+import android.os.Build
+import android.os.CombinedVibration
+import android.os.VibrationEffect
+import android.os.Vibrator
+import android.os.VibratorManager
+import android.view.InputDevice
+import androidx.annotation.Keep
+import androidx.annotation.RequiresApi
+import org.yuzu.yuzu_emu.YuzuApplication
+
+@Keep
+@Suppress("DEPRECATION")
+interface YuzuVibrator {
+ fun supportsVibration(): Boolean
+
+ fun vibrate(intensity: Float)
+
+ companion object {
+ fun getControllerVibrator(device: InputDevice): YuzuVibrator =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ YuzuVibratorManager(device.vibratorManager)
+ } else {
+ YuzuVibratorManagerCompat(device.vibrator)
+ }
+
+ fun getSystemVibrator(): YuzuVibrator =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ val vibratorManager = YuzuApplication.appContext
+ .getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
+ YuzuVibratorManager(vibratorManager)
+ } else {
+ val vibrator = YuzuApplication.appContext
+ .getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+ YuzuVibratorManagerCompat(vibrator)
+ }
+
+ fun getVibrationEffect(intensity: Float): VibrationEffect? {
+ if (intensity > 0f) {
+ return VibrationEffect.createOneShot(
+ 50,
+ (255.0 * intensity).toInt().coerceIn(1, 255)
+ )
+ }
+ return null
+ }
+ }
+}
+
+@RequiresApi(Build.VERSION_CODES.S)
+class YuzuVibratorManager(private val vibratorManager: VibratorManager) : YuzuVibrator {
+ override fun supportsVibration(): Boolean {
+ return vibratorManager.vibratorIds.isNotEmpty()
+ }
+
+ override fun vibrate(intensity: Float) {
+ val vibration = YuzuVibrator.getVibrationEffect(intensity) ?: return
+ vibratorManager.vibrate(CombinedVibration.createParallel(vibration))
+ }
+}
+
+class YuzuVibratorManagerCompat(private val vibrator: Vibrator) : YuzuVibrator {
+ override fun supportsVibration(): Boolean {
+ return vibrator.hasVibrator()
+ }
+
+ override fun vibrate(intensity: Float) {
+ val vibration = YuzuVibrator.getVibrationEffect(intensity) ?: return
+ vibrator.vibrate(vibration)
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/AnalogDirection.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/AnalogDirection.kt
new file mode 100644
index 000000000..0a5fab2ae
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/AnalogDirection.kt
@@ -0,0 +1,11 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.input.model
+
+enum class AnalogDirection(val int: Int, val param: String) {
+ Up(0, "up"),
+ Down(1, "down"),
+ Left(2, "left"),
+ Right(3, "right")
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/ButtonName.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/ButtonName.kt
new file mode 100644
index 000000000..b8846ecad
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/ButtonName.kt
@@ -0,0 +1,19 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.input.model
+
+// Loosely matches the enum in common/input.h
+enum class ButtonName(val int: Int) {
+ Invalid(1),
+
+ // This will display the engine name instead of the button name
+ Engine(2),
+
+ // This will display the button by value instead of the button name
+ Value(3);
+
+ companion object {
+ fun from(int: Int): ButtonName = entries.firstOrNull { it.int == int } ?: Invalid
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/InputType.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/InputType.kt
new file mode 100644
index 000000000..f725231cb
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/InputType.kt
@@ -0,0 +1,13 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.input.model
+
+// Must match the corresponding enum in input_common/main.h
+enum class InputType(val int: Int) {
+ None(0),
+ Button(1),
+ Stick(2),
+ Motion(3),
+ Touch(4)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeAnalog.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeAnalog.kt
new file mode 100644
index 000000000..c3b7a785d
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeAnalog.kt
@@ -0,0 +1,14 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.input.model
+
+// Must match enum in src/common/settings_input.h
+enum class NativeAnalog(val int: Int) {
+ LStick(0),
+ RStick(1);
+
+ companion object {
+ fun from(int: Int): NativeAnalog = entries.firstOrNull { it.int == int } ?: LStick
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeButton.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeButton.kt
new file mode 100644
index 000000000..c5ccd7115
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeButton.kt
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.input.model
+
+// Must match enum in src/common/settings_input.h
+enum class NativeButton(val int: Int) {
+ A(0),
+ B(1),
+ X(2),
+ Y(3),
+ LStick(4),
+ RStick(5),
+ L(6),
+ R(7),
+ ZL(8),
+ ZR(9),
+ Plus(10),
+ Minus(11),
+
+ DLeft(12),
+ DUp(13),
+ DRight(14),
+ DDown(15),
+
+ SLLeft(16),
+ SRLeft(17),
+
+ Home(18),
+ Capture(19),
+
+ SLRight(20),
+ SRRight(21);
+
+ companion object {
+ fun from(int: Int): NativeButton = entries.firstOrNull { it.int == int } ?: A
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeTrigger.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeTrigger.kt
new file mode 100644
index 000000000..625f352b4
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeTrigger.kt
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.input.model
+
+// Must match enum in src/common/settings_input.h
+enum class NativeTrigger(val int: Int) {
+ LTrigger(0),
+ RTrigger(1)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NpadStyleIndex.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NpadStyleIndex.kt
new file mode 100644
index 000000000..e2a3d7aff
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NpadStyleIndex.kt
@@ -0,0 +1,30 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.input.model
+
+import androidx.annotation.StringRes
+import org.yuzu.yuzu_emu.R
+
+// Must match enum in src/core/hid/hid_types.h
+enum class NpadStyleIndex(val int: Int, @StringRes val nameId: Int = 0) {
+ None(0),
+ Fullkey(3, R.string.pro_controller),
+ Handheld(4, R.string.handheld),
+ HandheldNES(4),
+ JoyconDual(5, R.string.dual_joycons),
+ JoyconLeft(6, R.string.left_joycon),
+ JoyconRight(7, R.string.right_joycon),
+ GameCube(8, R.string.gamecube_controller),
+ Pokeball(9),
+ NES(10),
+ SNES(12),
+ N64(13),
+ SegaGenesis(14),
+ SystemExt(32),
+ System(33);
+
+ companion object {
+ fun from(int: Int): NpadStyleIndex = entries.firstOrNull { it.int == int } ?: None
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/PlayerInput.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/PlayerInput.kt
new file mode 100644
index 000000000..d35de80c4
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/PlayerInput.kt
@@ -0,0 +1,83 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.input.model
+
+import androidx.annotation.Keep
+
+@Keep
+data class PlayerInput(
+ var connected: Boolean,
+ var buttons: Array<String>,
+ var analogs: Array<String>,
+ var motions: Array<String>,
+
+ var vibrationEnabled: Boolean,
+ var vibrationStrength: Int,
+
+ var bodyColorLeft: Long,
+ var bodyColorRight: Long,
+ var buttonColorLeft: Long,
+ var buttonColorRight: Long,
+ var profileName: String,
+
+ var useSystemVibrator: Boolean
+) {
+ // It's recommended to use the generated equals() and hashCode() methods
+ // when using arrays in a data class
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as PlayerInput
+
+ if (connected != other.connected) return false
+ if (!buttons.contentEquals(other.buttons)) return false
+ if (!analogs.contentEquals(other.analogs)) return false
+ if (!motions.contentEquals(other.motions)) return false
+ if (vibrationEnabled != other.vibrationEnabled) return false
+ if (vibrationStrength != other.vibrationStrength) return false
+ if (bodyColorLeft != other.bodyColorLeft) return false
+ if (bodyColorRight != other.bodyColorRight) return false
+ if (buttonColorLeft != other.buttonColorLeft) return false
+ if (buttonColorRight != other.buttonColorRight) return false
+ if (profileName != other.profileName) return false
+ return useSystemVibrator == other.useSystemVibrator
+ }
+
+ override fun hashCode(): Int {
+ var result = connected.hashCode()
+ result = 31 * result + buttons.contentHashCode()
+ result = 31 * result + analogs.contentHashCode()
+ result = 31 * result + motions.contentHashCode()
+ result = 31 * result + vibrationEnabled.hashCode()
+ result = 31 * result + vibrationStrength
+ result = 31 * result + bodyColorLeft.hashCode()
+ result = 31 * result + bodyColorRight.hashCode()
+ result = 31 * result + buttonColorLeft.hashCode()
+ result = 31 * result + buttonColorRight.hashCode()
+ result = 31 * result + profileName.hashCode()
+ result = 31 * result + useSystemVibrator.hashCode()
+ return result
+ }
+
+ fun hasMapping(): Boolean {
+ var hasMapping = false
+ buttons.forEach {
+ if (it != "[empty]") {
+ hasMapping = true
+ }
+ }
+ analogs.forEach {
+ if (it != "[empty]") {
+ hasMapping = true
+ }
+ }
+ motions.forEach {
+ if (it != "[empty]") {
+ hasMapping = true
+ }
+ }
+ return hasMapping
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index 862c6c483..4f6b93bd2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -4,17 +4,30 @@
package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
object Settings {
- enum class MenuTag(val titleId: Int) {
+ enum class MenuTag(val titleId: Int = 0) {
SECTION_ROOT(R.string.advanced_settings),
SECTION_SYSTEM(R.string.preferences_system),
SECTION_RENDERER(R.string.preferences_graphics),
SECTION_AUDIO(R.string.preferences_audio),
+ SECTION_INPUT(R.string.preferences_controls),
+ SECTION_INPUT_PLAYER_ONE,
+ SECTION_INPUT_PLAYER_TWO,
+ SECTION_INPUT_PLAYER_THREE,
+ SECTION_INPUT_PLAYER_FOUR,
+ SECTION_INPUT_PLAYER_FIVE,
+ SECTION_INPUT_PLAYER_SIX,
+ SECTION_INPUT_PLAYER_SEVEN,
+ SECTION_INPUT_PLAYER_EIGHT,
SECTION_THEME(R.string.preferences_theme),
SECTION_DEBUG(R.string.preferences_debug);
}
+ fun getPlayerString(player: Int): String =
+ YuzuApplication.appContext.getString(R.string.preferences_player, player)
+
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/AnalogInputSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/AnalogInputSetting.kt
new file mode 100644
index 000000000..a2996725e
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/AnalogInputSetting.kt
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model.view
+
+import androidx.annotation.StringRes
+import org.yuzu.yuzu_emu.features.input.NativeInput
+import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
+import org.yuzu.yuzu_emu.features.input.model.InputType
+import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
+import org.yuzu.yuzu_emu.utils.ParamPackage
+
+class AnalogInputSetting(
+ override val playerIndex: Int,
+ val nativeAnalog: NativeAnalog,
+ val analogDirection: AnalogDirection,
+ @StringRes titleId: Int = 0,
+ titleString: String = ""
+) : InputSetting(titleId, titleString) {
+ override val type = TYPE_INPUT
+ override val inputType = InputType.Stick
+
+ override fun getSelectedValue(): String {
+ val params = NativeInput.getStickParam(playerIndex, nativeAnalog)
+ val analog = analogToText(params, analogDirection.param)
+ return getDisplayString(params, analog)
+ }
+
+ override fun setSelectedValue(param: ParamPackage) =
+ NativeInput.setStickParam(playerIndex, nativeAnalog, param)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/ButtonInputSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/ButtonInputSetting.kt
new file mode 100644
index 000000000..786d09a7a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/ButtonInputSetting.kt
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model.view
+
+import androidx.annotation.StringRes
+import org.yuzu.yuzu_emu.utils.ParamPackage
+import org.yuzu.yuzu_emu.features.input.NativeInput
+import org.yuzu.yuzu_emu.features.input.model.InputType
+import org.yuzu.yuzu_emu.features.input.model.NativeButton
+
+class ButtonInputSetting(
+ override val playerIndex: Int,
+ val nativeButton: NativeButton,
+ @StringRes titleId: Int = 0,
+ titleString: String = ""
+) : InputSetting(titleId, titleString) {
+ override val type = TYPE_INPUT
+ override val inputType = InputType.Button
+
+ override fun getSelectedValue(): String {
+ val params = NativeInput.getButtonParam(playerIndex, nativeButton)
+ val button = buttonToText(params)
+ return getDisplayString(params, button)
+ }
+
+ override fun setSelectedValue(param: ParamPackage) =
+ NativeInput.setButtonParam(playerIndex, nativeButton, param)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
index 1d81f5f2b..58febff1d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
@@ -3,13 +3,16 @@
package org.yuzu.yuzu_emu.features.settings.model.view
+import androidx.annotation.StringRes
import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
class DateTimeSetting(
private val longSetting: AbstractLongSetting,
- titleId: Int,
- descriptionId: Int
-) : SettingsItem(longSetting, titleId, descriptionId) {
+ @StringRes titleId: Int = 0,
+ titleString: String = "",
+ @StringRes descriptionId: Int = 0,
+ descriptionString: String = ""
+) : SettingsItem(longSetting, titleId, titleString, descriptionId, descriptionString) {
override val type = TYPE_DATETIME_SETTING
fun getValue(needsGlobal: Boolean = false): Long = longSetting.getLong(needsGlobal)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
index d31ce1c31..8a6a51d5c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
@@ -3,8 +3,11 @@
package org.yuzu.yuzu_emu.features.settings.model.view
+import androidx.annotation.StringRes
+
class HeaderSetting(
- titleId: Int
-) : SettingsItem(emptySetting, titleId, 0) {
+ @StringRes titleId: Int = 0,
+ titleString: String = ""
+) : SettingsItem(emptySetting, titleId, titleString, 0, "") {
override val type = TYPE_HEADER
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/InputProfileSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/InputProfileSetting.kt
new file mode 100644
index 000000000..c46de08c5
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/InputProfileSetting.kt
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model.view
+
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.features.input.NativeInput
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+class InputProfileSetting(private val playerIndex: Int) :
+ SettingsItem(emptySetting, R.string.profile, "", 0, "") {
+ override val type = TYPE_INPUT_PROFILE
+
+ fun getCurrentProfile(): String =
+ NativeConfig.getInputSettings(true)[playerIndex].profileName
+
+ fun getProfileNames(): Array<String> = NativeInput.getInputProfileNames()
+
+ fun isProfileNameValid(name: String): Boolean = NativeInput.isProfileNameValid(name)
+
+ fun createProfile(name: String): Boolean = NativeInput.createProfile(name, playerIndex)
+
+ fun deleteProfile(name: String): Boolean = NativeInput.deleteProfile(name, playerIndex)
+
+ fun loadProfile(name: String): Boolean {
+ val result = NativeInput.loadProfile(name, playerIndex)
+ NativeInput.reloadInputDevices()
+ return result
+ }
+
+ fun saveProfile(name: String): Boolean = NativeInput.saveProfile(name, playerIndex)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/InputSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/InputSetting.kt
new file mode 100644
index 000000000..2d118bff3
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/InputSetting.kt
@@ -0,0 +1,134 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model.view
+
+import androidx.annotation.StringRes
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.features.input.NativeInput
+import org.yuzu.yuzu_emu.features.input.model.ButtonName
+import org.yuzu.yuzu_emu.features.input.model.InputType
+import org.yuzu.yuzu_emu.utils.ParamPackage
+
+sealed class InputSetting(
+ @StringRes titleId: Int,
+ titleString: String
+) : SettingsItem(emptySetting, titleId, titleString, 0, "") {
+ override val type = TYPE_INPUT
+ abstract val inputType: InputType
+ abstract val playerIndex: Int
+
+ protected val context get() = YuzuApplication.appContext
+
+ abstract fun getSelectedValue(): String
+
+ abstract fun setSelectedValue(param: ParamPackage)
+
+ protected fun getDisplayString(params: ParamPackage, control: String): String {
+ val deviceName = params.get("display", "")
+ deviceName.ifEmpty {
+ return context.getString(R.string.not_set)
+ }
+ return "$deviceName: $control"
+ }
+
+ private fun getDirectionName(direction: String): String =
+ when (direction) {
+ "up" -> context.getString(R.string.up)
+ "down" -> context.getString(R.string.down)
+ "left" -> context.getString(R.string.left)
+ "right" -> context.getString(R.string.right)
+ else -> direction
+ }
+
+ protected fun buttonToText(param: ParamPackage): String {
+ if (!param.has("engine")) {
+ return context.getString(R.string.not_set)
+ }
+
+ val toggle = if (param.get("toggle", false)) "~" else ""
+ val inverted = if (param.get("inverted", false)) "!" else ""
+ val invert = if (param.get("invert", "+") == "-") "-" else ""
+ val turbo = if (param.get("turbo", false)) "$" else ""
+ val commonButtonName = NativeInput.getButtonName(param)
+
+ if (commonButtonName == ButtonName.Invalid) {
+ return context.getString(R.string.invalid)
+ }
+
+ if (commonButtonName == ButtonName.Engine) {
+ return param.get("engine", "")
+ }
+
+ if (commonButtonName == ButtonName.Value) {
+ if (param.has("hat")) {
+ val hat = getDirectionName(param.get("direction", ""))
+ return context.getString(R.string.qualified_hat, turbo, toggle, inverted, hat)
+ }
+ if (param.has("axis")) {
+ val axis = param.get("axis", "")
+ return context.getString(
+ R.string.qualified_button_stick_axis,
+ toggle,
+ inverted,
+ invert,
+ axis
+ )
+ }
+ if (param.has("button")) {
+ val button = param.get("button", "")
+ return context.getString(R.string.qualified_button, turbo, toggle, inverted, button)
+ }
+ }
+
+ return context.getString(R.string.unknown)
+ }
+
+ protected fun analogToText(param: ParamPackage, direction: String): String {
+ if (!param.has("engine")) {
+ return context.getString(R.string.not_set)
+ }
+
+ if (param.get("engine", "") == "analog_from_button") {
+ return buttonToText(ParamPackage(param.get(direction, "")))
+ }
+
+ if (!param.has("axis_x") || !param.has("axis_y")) {
+ return context.getString(R.string.unknown)
+ }
+
+ val xAxis = param.get("axis_x", "")
+ val yAxis = param.get("axis_y", "")
+ val xInvert = param.get("invert_x", "+") == "-"
+ val yInvert = param.get("invert_y", "+") == "-"
+
+ if (direction == "modifier") {
+ return context.getString(R.string.unused)
+ }
+
+ when (direction) {
+ "up" -> {
+ val yInvertString = if (yInvert) "+" else "-"
+ return context.getString(R.string.qualified_axis, yAxis, yInvertString)
+ }
+
+ "down" -> {
+ val yInvertString = if (yInvert) "-" else "+"
+ return context.getString(R.string.qualified_axis, yAxis, yInvertString)
+ }
+
+ "left" -> {
+ val xInvertString = if (xInvert) "+" else "-"
+ return context.getString(R.string.qualified_axis, xAxis, xInvertString)
+ }
+
+ "right" -> {
+ val xInvertString = if (xInvert) "-" else "+"
+ return context.getString(R.string.qualified_axis, xAxis, xInvertString)
+ }
+ }
+
+ return context.getString(R.string.unknown)
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/IntSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/IntSingleChoiceSetting.kt
new file mode 100644
index 000000000..e024c793a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/IntSingleChoiceSetting.kt
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model.view
+
+import androidx.annotation.StringRes
+import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
+
+class IntSingleChoiceSetting(
+ private val intSetting: AbstractIntSetting,
+ @StringRes titleId: Int = 0,
+ titleString: String = "",
+ @StringRes descriptionId: Int = 0,
+ descriptionString: String = "",
+ val choices: Array<String>,
+ val values: Array<Int>
+) : SettingsItem(intSetting, titleId, titleString, descriptionId, descriptionString) {
+ override val type = TYPE_INT_SINGLE_CHOICE
+
+ fun getValueAt(index: Int): Int =
+ if (values.indices.contains(index)) values[index] else -1
+
+ fun getChoiceAt(index: Int): String =
+ if (choices.indices.contains(index)) choices[index] else ""
+
+ fun getSelectedValue(needsGlobal: Boolean = false) = intSetting.getInt(needsGlobal)
+ fun setSelectedValue(value: Int) = intSetting.setInt(value)
+
+ val selectedValueIndex: Int
+ get() {
+ for (i in values.indices) {
+ if (values[i] == getSelectedValue()) {
+ return i
+ }
+ }
+ return -1
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/ModifierInputSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/ModifierInputSetting.kt
new file mode 100644
index 000000000..a1db3cc87
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/ModifierInputSetting.kt
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model.view
+
+import androidx.annotation.StringRes
+import org.yuzu.yuzu_emu.features.input.NativeInput
+import org.yuzu.yuzu_emu.features.input.model.InputType
+import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
+import org.yuzu.yuzu_emu.utils.ParamPackage
+
+class ModifierInputSetting(
+ override val playerIndex: Int,
+ val nativeAnalog: NativeAnalog,
+ @StringRes titleId: Int = 0,
+ titleString: String = ""
+) : InputSetting(titleId, titleString) {
+ override val inputType = InputType.Button
+
+ override fun getSelectedValue(): String {
+ val analogParam = NativeInput.getStickParam(playerIndex, nativeAnalog)
+ val modifierParam = ParamPackage(analogParam.get("modifier", ""))
+ return buttonToText(modifierParam)
+ }
+
+ override fun setSelectedValue(param: ParamPackage) {
+ val newParam = NativeInput.getStickParam(playerIndex, nativeAnalog)
+ newParam.set("modifier", param.serialize())
+ NativeInput.setStickParam(playerIndex, nativeAnalog, newParam)
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
index 425160024..06f607424 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
@@ -4,13 +4,16 @@
package org.yuzu.yuzu_emu.features.settings.model.view
import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
class RunnableSetting(
- titleId: Int,
- descriptionId: Int,
- val isRuntimeRunnable: Boolean,
+ @StringRes titleId: Int = 0,
+ titleString: String = "",
+ @StringRes descriptionId: Int = 0,
+ descriptionString: String = "",
+ val isRunnable: Boolean,
@DrawableRes val iconId: Int = 0,
val runnable: () -> Unit
-) : SettingsItem(emptySetting, titleId, descriptionId) {
+) : SettingsItem(emptySetting, titleId, titleString, descriptionId, descriptionString) {
override val type = TYPE_RUNNABLE
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index 21ca97bc1..8f724835e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -3,8 +3,12 @@
package org.yuzu.yuzu_emu.features.settings.model.view
+import androidx.annotation.StringRes
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.features.input.NativeInput
+import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
@@ -23,13 +27,34 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
*/
abstract class SettingsItem(
val setting: AbstractSetting,
- val nameId: Int,
- val descriptionId: Int
+ @StringRes val titleId: Int,
+ val titleString: String,
+ @StringRes val descriptionId: Int,
+ val descriptionString: String
) {
abstract val type: Int
+ val title: String by lazy {
+ if (titleId != 0) {
+ return@lazy YuzuApplication.appContext.getString(titleId)
+ }
+ return@lazy titleString
+ }
+
+ val description: String by lazy {
+ if (descriptionId != 0) {
+ return@lazy YuzuApplication.appContext.getString(descriptionId)
+ }
+ return@lazy descriptionString
+ }
+
val isEditable: Boolean
get() {
+ // Can't change docked mode toggle when using handheld mode
+ if (setting.key == BooleanSetting.USE_DOCKED_MODE.key) {
+ return NativeInput.getStyleIndex(0) != NpadStyleIndex.Handheld
+ }
+
// Can't edit settings that aren't saveable in per-game config even if they are switchable
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
return false
@@ -59,6 +84,9 @@ abstract class SettingsItem(
const val TYPE_STRING_SINGLE_CHOICE = 5
const val TYPE_DATETIME_SETTING = 6
const val TYPE_RUNNABLE = 7
+ const val TYPE_INPUT = 8
+ const val TYPE_INT_SINGLE_CHOICE = 9
+ const val TYPE_INPUT_PROFILE = 10
const val FASTMEM_COMBINED = "fastmem_combined"
@@ -80,237 +108,242 @@ abstract class SettingsItem(
put(
SwitchSetting(
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
- R.string.frame_limit_enable,
- R.string.frame_limit_enable_description
+ titleId = R.string.frame_limit_enable,
+ descriptionId = R.string.frame_limit_enable_description
)
)
put(
SliderSetting(
ShortSetting.RENDERER_SPEED_LIMIT,
- R.string.frame_limit_slider,
- R.string.frame_limit_slider_description,
- 1,
- 400,
- "%"
+ titleId = R.string.frame_limit_slider,
+ descriptionId = R.string.frame_limit_slider_description,
+ min = 1,
+ max = 400,
+ units = "%"
)
)
put(
SingleChoiceSetting(
IntSetting.CPU_BACKEND,
- R.string.cpu_backend,
- 0,
- R.array.cpuBackendArm64Names,
- R.array.cpuBackendArm64Values
+ titleId = R.string.cpu_backend,
+ choicesId = R.array.cpuBackendArm64Names,
+ valuesId = R.array.cpuBackendArm64Values
)
)
put(
SingleChoiceSetting(
IntSetting.CPU_ACCURACY,
- R.string.cpu_accuracy,
- 0,
- R.array.cpuAccuracyNames,
- R.array.cpuAccuracyValues
+ titleId = R.string.cpu_accuracy,
+ choicesId = R.array.cpuAccuracyNames,
+ valuesId = R.array.cpuAccuracyValues
)
)
put(
SwitchSetting(
BooleanSetting.PICTURE_IN_PICTURE,
- R.string.picture_in_picture,
- R.string.picture_in_picture_description
+ titleId = R.string.picture_in_picture,
+ descriptionId = R.string.picture_in_picture_description
)
)
+
+ val dockedModeSetting = object : AbstractBooleanSetting {
+ override val key = BooleanSetting.USE_DOCKED_MODE.key
+
+ override fun getBoolean(needsGlobal: Boolean): Boolean {
+ if (NativeInput.getStyleIndex(0) == NpadStyleIndex.Handheld) {
+ return false
+ }
+ return BooleanSetting.USE_DOCKED_MODE.getBoolean(needsGlobal)
+ }
+
+ override fun setBoolean(value: Boolean) =
+ BooleanSetting.USE_DOCKED_MODE.setBoolean(value)
+
+ override val defaultValue = BooleanSetting.USE_DOCKED_MODE.defaultValue
+
+ override fun getValueAsString(needsGlobal: Boolean): String =
+ BooleanSetting.USE_DOCKED_MODE.getValueAsString(needsGlobal)
+
+ override fun reset() = BooleanSetting.USE_DOCKED_MODE.reset()
+ }
put(
SwitchSetting(
- BooleanSetting.USE_DOCKED_MODE,
- R.string.use_docked_mode,
- R.string.use_docked_mode_description
+ dockedModeSetting,
+ titleId = R.string.use_docked_mode,
+ descriptionId = R.string.use_docked_mode_description
)
)
+
put(
SingleChoiceSetting(
IntSetting.REGION_INDEX,
- R.string.emulated_region,
- 0,
- R.array.regionNames,
- R.array.regionValues
+ titleId = R.string.emulated_region,
+ choicesId = R.array.regionNames,
+ valuesId = R.array.regionValues
)
)
put(
SingleChoiceSetting(
IntSetting.LANGUAGE_INDEX,
- R.string.emulated_language,
- 0,
- R.array.languageNames,
- R.array.languageValues
+ titleId = R.string.emulated_language,
+ choicesId = R.array.languageNames,
+ valuesId = R.array.languageValues
)
)
put(
SwitchSetting(
BooleanSetting.USE_CUSTOM_RTC,
- R.string.use_custom_rtc,
- R.string.use_custom_rtc_description
+ titleId = R.string.use_custom_rtc,
+ descriptionId = R.string.use_custom_rtc_description
)
)
- put(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0))
+ put(DateTimeSetting(LongSetting.CUSTOM_RTC, titleId = R.string.set_custom_rtc))
put(
SingleChoiceSetting(
IntSetting.RENDERER_ACCURACY,
- R.string.renderer_accuracy,
- 0,
- R.array.rendererAccuracyNames,
- R.array.rendererAccuracyValues
+ titleId = R.string.renderer_accuracy,
+ choicesId = R.array.rendererAccuracyNames,
+ valuesId = R.array.rendererAccuracyValues
)
)
put(
SingleChoiceSetting(
IntSetting.RENDERER_RESOLUTION,
- R.string.renderer_resolution,
- 0,
- R.array.rendererResolutionNames,
- R.array.rendererResolutionValues
+ titleId = R.string.renderer_resolution,
+ choicesId = R.array.rendererResolutionNames,
+ valuesId = R.array.rendererResolutionValues
)
)
put(
SingleChoiceSetting(
IntSetting.RENDERER_VSYNC,
- R.string.renderer_vsync,
- 0,
- R.array.rendererVSyncNames,
- R.array.rendererVSyncValues
+ titleId = R.string.renderer_vsync,
+ choicesId = R.array.rendererVSyncNames,
+ valuesId = R.array.rendererVSyncValues
)
)
put(
SingleChoiceSetting(
IntSetting.RENDERER_SCALING_FILTER,
- R.string.renderer_scaling_filter,
- 0,
- R.array.rendererScalingFilterNames,
- R.array.rendererScalingFilterValues
+ titleId = R.string.renderer_scaling_filter,
+ choicesId = R.array.rendererScalingFilterNames,
+ valuesId = R.array.rendererScalingFilterValues
)
)
put(
SliderSetting(
IntSetting.FSR_SHARPENING_SLIDER,
- R.string.fsr_sharpness,
- R.string.fsr_sharpness_description,
- 0,
- 100,
- "%"
+ titleId = R.string.fsr_sharpness,
+ descriptionId = R.string.fsr_sharpness_description,
+ units = "%"
)
)
put(
SingleChoiceSetting(
IntSetting.RENDERER_ANTI_ALIASING,
- R.string.renderer_anti_aliasing,
- 0,
- R.array.rendererAntiAliasingNames,
- R.array.rendererAntiAliasingValues
+ titleId = R.string.renderer_anti_aliasing,
+ choicesId = R.array.rendererAntiAliasingNames,
+ valuesId = R.array.rendererAntiAliasingValues
)
)
put(
SingleChoiceSetting(
IntSetting.RENDERER_SCREEN_LAYOUT,
- R.string.renderer_screen_layout,
- 0,
- R.array.rendererScreenLayoutNames,
- R.array.rendererScreenLayoutValues
+ titleId = R.string.renderer_screen_layout,
+ choicesId = R.array.rendererScreenLayoutNames,
+ valuesId = R.array.rendererScreenLayoutValues
)
)
put(
SingleChoiceSetting(
IntSetting.RENDERER_ASPECT_RATIO,
- R.string.renderer_aspect_ratio,
- 0,
- R.array.rendererAspectRatioNames,
- R.array.rendererAspectRatioValues
+ titleId = R.string.renderer_aspect_ratio,
+ choicesId = R.array.rendererAspectRatioNames,
+ valuesId = R.array.rendererAspectRatioValues
)
)
put(
SingleChoiceSetting(
IntSetting.VERTICAL_ALIGNMENT,
- R.string.vertical_alignment,
- 0,
- R.array.verticalAlignmentEntries,
- R.array.verticalAlignmentValues
+ titleId = R.string.vertical_alignment,
+ descriptionId = 0,
+ choicesId = R.array.verticalAlignmentEntries,
+ valuesId = R.array.verticalAlignmentValues
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
- R.string.use_disk_shader_cache,
- R.string.use_disk_shader_cache_description
+ titleId = R.string.use_disk_shader_cache,
+ descriptionId = R.string.use_disk_shader_cache_description
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
- R.string.renderer_force_max_clock,
- R.string.renderer_force_max_clock_description
+ titleId = R.string.renderer_force_max_clock,
+ descriptionId = R.string.renderer_force_max_clock_description
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
- R.string.renderer_asynchronous_shaders,
- R.string.renderer_asynchronous_shaders_description
+ titleId = R.string.renderer_asynchronous_shaders,
+ descriptionId = R.string.renderer_asynchronous_shaders_description
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_REACTIVE_FLUSHING,
- R.string.renderer_reactive_flushing,
- R.string.renderer_reactive_flushing_description
+ titleId = R.string.renderer_reactive_flushing,
+ descriptionId = R.string.renderer_reactive_flushing_description
)
)
put(
SingleChoiceSetting(
IntSetting.MAX_ANISOTROPY,
- R.string.anisotropic_filtering,
- R.string.anisotropic_filtering_description,
- R.array.anisoEntries,
- R.array.anisoValues
+ titleId = R.string.anisotropic_filtering,
+ descriptionId = R.string.anisotropic_filtering_description,
+ choicesId = R.array.anisoEntries,
+ valuesId = R.array.anisoValues
)
)
put(
SingleChoiceSetting(
IntSetting.AUDIO_OUTPUT_ENGINE,
- R.string.audio_output_engine,
- 0,
- R.array.outputEngineEntries,
- R.array.outputEngineValues
+ titleId = R.string.audio_output_engine,
+ choicesId = R.array.outputEngineEntries,
+ valuesId = R.array.outputEngineValues
)
)
put(
SliderSetting(
ByteSetting.AUDIO_VOLUME,
- R.string.audio_volume,
- R.string.audio_volume_description,
- 0,
- 100,
- "%"
+ titleId = R.string.audio_volume,
+ descriptionId = R.string.audio_volume_description,
+ units = "%"
)
)
put(
SingleChoiceSetting(
IntSetting.RENDERER_BACKEND,
- R.string.renderer_api,
- 0,
- R.array.rendererApiNames,
- R.array.rendererApiValues
+ titleId = R.string.renderer_api,
+ choicesId = R.array.rendererApiNames,
+ valuesId = R.array.rendererApiValues
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_DEBUG,
- R.string.renderer_debug,
- R.string.renderer_debug_description
+ titleId = R.string.renderer_debug,
+ descriptionId = R.string.renderer_debug_description
)
)
put(
SwitchSetting(
BooleanSetting.CPU_DEBUG_MODE,
- R.string.cpu_debug_mode,
- R.string.cpu_debug_mode_description
+ titleId = R.string.cpu_debug_mode,
+ descriptionId = R.string.cpu_debug_mode_description
)
)
@@ -346,7 +379,7 @@ abstract class SettingsItem(
override fun reset() = setBoolean(defaultValue)
}
- put(SwitchSetting(fastmem, R.string.fastmem, 0))
+ put(SwitchSetting(fastmem, R.string.fastmem))
}
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
index 97a5a9e59..ea5e099ed 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
@@ -3,16 +3,20 @@
package org.yuzu.yuzu_emu.features.settings.model.view
+import androidx.annotation.ArrayRes
+import androidx.annotation.StringRes
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
class SingleChoiceSetting(
setting: AbstractSetting,
- titleId: Int,
- descriptionId: Int,
- val choicesId: Int,
- val valuesId: Int
-) : SettingsItem(setting, titleId, descriptionId) {
+ @StringRes titleId: Int = 0,
+ titleString: String = "",
+ @StringRes descriptionId: Int = 0,
+ descriptionString: String = "",
+ @ArrayRes val choicesId: Int,
+ @ArrayRes val valuesId: Int
+) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
override val type = TYPE_SINGLE_CHOICE
fun getSelectedValue(needsGlobal: Boolean = false) =
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
index b9b709bf7..6a5cdf48b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
@@ -3,6 +3,7 @@
package org.yuzu.yuzu_emu.features.settings.model.view
+import androidx.annotation.StringRes
import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
@@ -12,12 +13,14 @@ import kotlin.math.roundToInt
class SliderSetting(
setting: AbstractSetting,
- titleId: Int,
- descriptionId: Int,
- val min: Int,
- val max: Int,
- val units: String
-) : SettingsItem(setting, titleId, descriptionId) {
+ @StringRes titleId: Int = 0,
+ titleString: String = "",
+ @StringRes descriptionId: Int = 0,
+ descriptionString: String = "",
+ val min: Int = 0,
+ val max: Int = 100,
+ val units: String = ""
+) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
override val type = TYPE_SLIDER
fun getSelectedValue(needsGlobal: Boolean = false) =
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
index ba7920f50..5260ff4dc 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
@@ -3,15 +3,18 @@
package org.yuzu.yuzu_emu.features.settings.model.view
+import androidx.annotation.StringRes
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
class StringSingleChoiceSetting(
private val stringSetting: AbstractStringSetting,
- titleId: Int,
- descriptionId: Int,
+ @StringRes titleId: Int = 0,
+ titleString: String = "",
+ @StringRes descriptionId: Int = 0,
+ descriptionString: String = "",
val choices: Array<String>,
val values: Array<String>
-) : SettingsItem(stringSetting, titleId, descriptionId) {
+) : SettingsItem(stringSetting, titleId, titleString, descriptionId, descriptionString) {
override val type = TYPE_STRING_SINGLE_CHOICE
fun getValueAt(index: Int): String =
@@ -20,7 +23,7 @@ class StringSingleChoiceSetting(
fun getSelectedValue(needsGlobal: Boolean = false) = stringSetting.getString(needsGlobal)
fun setSelectedValue(value: String) = stringSetting.setString(value)
- val selectValueIndex: Int
+ val selectedValueIndex: Int
get() {
for (i in values.indices) {
if (values[i] == getSelectedValue()) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
index 94953b18a..c722393dd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
@@ -8,10 +8,12 @@ import androidx.annotation.StringRes
import org.yuzu.yuzu_emu.features.settings.model.Settings
class SubmenuSetting(
- @StringRes titleId: Int,
- @StringRes descriptionId: Int,
- @DrawableRes val iconId: Int,
+ @StringRes titleId: Int = 0,
+ titleString: String = "",
+ @StringRes descriptionId: Int = 0,
+ descriptionString: String = "",
+ @DrawableRes val iconId: Int = 0,
val menuKey: Settings.MenuTag
-) : SettingsItem(emptySetting, titleId, descriptionId) {
+) : SettingsItem(emptySetting, titleId, titleString, descriptionId, descriptionString) {
override val type = TYPE_SUBMENU
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
index 44d47dd69..4984bf52e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
@@ -3,15 +3,18 @@
package org.yuzu.yuzu_emu.features.settings.model.view
+import androidx.annotation.StringRes
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
class SwitchSetting(
setting: AbstractSetting,
- titleId: Int,
- descriptionId: Int
-) : SettingsItem(setting, titleId, descriptionId) {
+ @StringRes titleId: Int = 0,
+ titleString: String = "",
+ @StringRes descriptionId: Int = 0,
+ descriptionString: String = ""
+) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
override val type = TYPE_SWITCH
fun getIsChecked(needsGlobal: Boolean = false): Boolean {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputDialogFragment.kt
new file mode 100644
index 000000000..16a1d0504
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputDialogFragment.kt
@@ -0,0 +1,300 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.ui
+
+import android.app.Dialog
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.view.InputDevice
+import android.view.KeyEvent
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.activityViewModels
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.DialogMappingBinding
+import org.yuzu.yuzu_emu.features.input.NativeInput
+import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
+import org.yuzu.yuzu_emu.features.input.model.NativeButton
+import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.ButtonInputSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.InputSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.ModifierInputSetting
+import org.yuzu.yuzu_emu.utils.InputHandler
+import org.yuzu.yuzu_emu.utils.ParamPackage
+
+class InputDialogFragment : DialogFragment() {
+ private var inputAccepted = false
+
+ private var position: Int = 0
+
+ private lateinit var inputSetting: InputSetting
+
+ private lateinit var binding: DialogMappingBinding
+
+ private val settingsViewModel: SettingsViewModel by activityViewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (settingsViewModel.clickedItem == null) dismiss()
+
+ position = requireArguments().getInt(POSITION)
+
+ InputHandler.updateControllerData()
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ inputSetting = settingsViewModel.clickedItem as InputSetting
+ binding = DialogMappingBinding.inflate(layoutInflater)
+
+ val builder = MaterialAlertDialogBuilder(requireContext())
+ .setPositiveButton(android.R.string.cancel) { _, _ ->
+ NativeInput.stopMapping()
+ dismiss()
+ }
+ .setView(binding.root)
+
+ val playButtonMapAnimation = { twoDirections: Boolean ->
+ val stickAnimation: AnimatedVectorDrawable
+ val buttonAnimation: AnimatedVectorDrawable
+ binding.imageStickAnimation.apply {
+ val anim = if (twoDirections) {
+ R.drawable.stick_two_direction_anim
+ } else {
+ R.drawable.stick_one_direction_anim
+ }
+ setBackgroundResource(anim)
+ stickAnimation = background as AnimatedVectorDrawable
+ }
+ binding.imageButtonAnimation.apply {
+ setBackgroundResource(R.drawable.button_anim)
+ buttonAnimation = background as AnimatedVectorDrawable
+ }
+ stickAnimation.registerAnimationCallback(object : Animatable2.AnimationCallback() {
+ override fun onAnimationEnd(drawable: Drawable?) {
+ buttonAnimation.start()
+ }
+ })
+ buttonAnimation.registerAnimationCallback(object : Animatable2.AnimationCallback() {
+ override fun onAnimationEnd(drawable: Drawable?) {
+ stickAnimation.start()
+ }
+ })
+ stickAnimation.start()
+ }
+
+ when (val setting = inputSetting) {
+ is AnalogInputSetting -> {
+ when (setting.nativeAnalog) {
+ NativeAnalog.LStick -> builder.setTitle(
+ getString(R.string.map_control, getString(R.string.left_stick))
+ )
+
+ NativeAnalog.RStick -> builder.setTitle(
+ getString(R.string.map_control, getString(R.string.right_stick))
+ )
+ }
+
+ builder.setMessage(R.string.stick_map_description)
+
+ playButtonMapAnimation.invoke(true)
+ }
+
+ is ModifierInputSetting -> {
+ builder.setTitle(getString(R.string.map_control, setting.title))
+ .setMessage(R.string.button_map_description)
+ playButtonMapAnimation.invoke(false)
+ }
+
+ is ButtonInputSetting -> {
+ if (setting.nativeButton == NativeButton.DUp ||
+ setting.nativeButton == NativeButton.DDown ||
+ setting.nativeButton == NativeButton.DLeft ||
+ setting.nativeButton == NativeButton.DRight
+ ) {
+ builder.setTitle(getString(R.string.map_dpad_direction, setting.title))
+ } else {
+ builder.setTitle(getString(R.string.map_control, setting.title))
+ }
+ builder.setMessage(R.string.button_map_description)
+ playButtonMapAnimation.invoke(false)
+ }
+ }
+
+ return builder.create()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ view.requestFocus()
+ view.setOnFocusChangeListener { v, hasFocus -> if (!hasFocus) v.requestFocus() }
+ dialog?.setOnKeyListener { _, _, keyEvent -> onKeyEvent(keyEvent) }
+ binding.root.setOnGenericMotionListener { _, motionEvent -> onMotionEvent(motionEvent) }
+ NativeInput.beginMapping(inputSetting.inputType.int)
+ }
+
+ private fun onKeyEvent(event: KeyEvent): Boolean {
+ if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
+ event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD
+ ) {
+ return false
+ }
+
+ val action = when (event.action) {
+ KeyEvent.ACTION_DOWN -> NativeInput.ButtonState.PRESSED
+ KeyEvent.ACTION_UP -> NativeInput.ButtonState.RELEASED
+ else -> return false
+ }
+ val controllerData =
+ InputHandler.androidControllers[event.device.controllerNumber] ?: return false
+ NativeInput.onGamePadButtonEvent(
+ controllerData.getGUID(),
+ controllerData.getPort(),
+ event.keyCode,
+ action
+ )
+ onInputReceived(event.device)
+ return true
+ }
+
+ private fun onMotionEvent(event: MotionEvent): Boolean {
+ if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
+ event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD
+ ) {
+ return false
+ }
+
+ // Temp workaround for DPads that give both axis and button input. The input system can't
+ // take in a specific axis direction for a binding so you lose half of the directions for a DPad.
+
+ val controllerData =
+ InputHandler.androidControllers[event.device.controllerNumber] ?: return false
+ event.device.motionRanges.forEach {
+ NativeInput.onGamePadAxisEvent(
+ controllerData.getGUID(),
+ controllerData.getPort(),
+ it.axis,
+ event.getAxisValue(it.axis)
+ )
+ onInputReceived(event.device)
+ }
+ return true
+ }
+
+ private fun onInputReceived(device: InputDevice) {
+ val params = ParamPackage(NativeInput.getNextInput())
+ if (params.has("engine") && isInputAcceptable(params) && !inputAccepted) {
+ inputAccepted = true
+ setResult(params, device)
+ }
+ }
+
+ private fun setResult(params: ParamPackage, device: InputDevice) {
+ NativeInput.stopMapping()
+ params.set("display", "${device.name} ${params.get("port", 0)}")
+ when (val item = settingsViewModel.clickedItem as InputSetting) {
+ is ModifierInputSetting,
+ is ButtonInputSetting -> {
+ // Invert DPad up and left bindings by default
+ val tempSetting = inputSetting as? ButtonInputSetting
+ if (tempSetting != null) {
+ if (tempSetting.nativeButton == NativeButton.DUp ||
+ tempSetting.nativeButton == NativeButton.DLeft &&
+ params.has("axis")
+ ) {
+ params.set("invert", "-")
+ }
+ }
+
+ item.setSelectedValue(params)
+ settingsViewModel.setAdapterItemChanged(position)
+ }
+
+ is AnalogInputSetting -> {
+ var analogParam = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog)
+ analogParam = adjustAnalogParam(params, analogParam, item.analogDirection.param)
+
+ // Invert Y-Axis by default
+ analogParam.set("invert_y", "-")
+
+ item.setSelectedValue(analogParam)
+ settingsViewModel.setReloadListAndNotifyDataset(true)
+ }
+ }
+ dismiss()
+ }
+
+ private fun adjustAnalogParam(
+ inputParam: ParamPackage,
+ analogParam: ParamPackage,
+ buttonName: String
+ ): ParamPackage {
+ // The poller returned a complete axis, so set all the buttons
+ if (inputParam.has("axis_x") && inputParam.has("axis_y")) {
+ return inputParam
+ }
+
+ // Check if the current configuration has either no engine or an axis binding.
+ // Clears out the old binding and adds one with analog_from_button.
+ if (!analogParam.has("engine") || analogParam.has("axis_x") || analogParam.has("axis_y")) {
+ analogParam.clear()
+ analogParam.set("engine", "analog_from_button")
+ }
+ analogParam.set(buttonName, inputParam.serialize())
+ return analogParam
+ }
+
+ private fun isInputAcceptable(params: ParamPackage): Boolean {
+ if (InputHandler.registeredControllers.size == 1) {
+ return true
+ }
+
+ if (params.has("motion")) {
+ return true
+ }
+
+ val currentDevice = settingsViewModel.getCurrentDeviceParams(params)
+ if (currentDevice.get("engine", "any") == "any") {
+ return true
+ }
+
+ val guidMatch = params.get("guid", "") == currentDevice.get("guid", "") ||
+ params.get("guid", "") == currentDevice.get("guid2", "")
+ return params.get("engine", "") == currentDevice.get("engine", "") &&
+ guidMatch &&
+ params.get("port", 0) == currentDevice.get("port", 0)
+ }
+
+ companion object {
+ const val TAG = "InputDialogFragment"
+
+ const val POSITION = "Position"
+
+ fun newInstance(
+ inputMappingViewModel: SettingsViewModel,
+ setting: InputSetting,
+ position: Int
+ ): InputDialogFragment {
+ inputMappingViewModel.clickedItem = setting
+ val args = Bundle()
+ args.putInt(POSITION, position)
+ val fragment = InputDialogFragment()
+ fragment.arguments = args
+ return fragment
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileAdapter.kt
new file mode 100644
index 000000000..5656e9d8d
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileAdapter.kt
@@ -0,0 +1,68 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.ui
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.adapters.AbstractListAdapter
+import org.yuzu.yuzu_emu.databinding.ListItemInputProfileBinding
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
+import org.yuzu.yuzu_emu.R
+
+class InputProfileAdapter(options: List<ProfileItem>) :
+ AbstractListAdapter<ProfileItem, AbstractViewHolder<ProfileItem>>(options) {
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): AbstractViewHolder<ProfileItem> {
+ ListItemInputProfileBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ .also { return InputProfileViewHolder(it) }
+ }
+
+ inner class InputProfileViewHolder(val binding: ListItemInputProfileBinding) :
+ AbstractViewHolder<ProfileItem>(binding) {
+ override fun bind(model: ProfileItem) {
+ when (model) {
+ is ExistingProfileItem -> {
+ binding.title.text = model.name
+ binding.buttonNew.visibility = View.GONE
+ binding.buttonDelete.visibility = View.VISIBLE
+ binding.buttonDelete.setOnClickListener { model.deleteProfile.invoke() }
+ binding.buttonSave.visibility = View.VISIBLE
+ binding.buttonSave.setOnClickListener { model.saveProfile.invoke() }
+ binding.buttonLoad.visibility = View.VISIBLE
+ binding.buttonLoad.setOnClickListener { model.loadProfile.invoke() }
+ }
+
+ is NewProfileItem -> {
+ binding.title.text = model.name
+ binding.buttonNew.visibility = View.VISIBLE
+ binding.buttonNew.setOnClickListener { model.createNewProfile.invoke() }
+ binding.buttonSave.visibility = View.GONE
+ binding.buttonDelete.visibility = View.GONE
+ binding.buttonLoad.visibility = View.GONE
+ }
+ }
+ }
+ }
+}
+
+sealed interface ProfileItem {
+ val name: String
+}
+
+data class NewProfileItem(
+ val createNewProfile: () -> Unit
+) : ProfileItem {
+ override val name: String = YuzuApplication.appContext.getString(R.string.create_new_profile)
+}
+
+data class ExistingProfileItem(
+ override val name: String,
+ val deleteProfile: () -> Unit,
+ val saveProfile: () -> Unit,
+ val loadProfile: () -> Unit
+) : ProfileItem
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileDialogFragment.kt
new file mode 100644
index 000000000..1bae593ae
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileDialogFragment.kt
@@ -0,0 +1,148 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.ui
+
+import android.app.Dialog
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.activityViewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.DialogInputProfilesBinding
+import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting
+import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
+import org.yuzu.yuzu_emu.utils.collect
+
+class InputProfileDialogFragment : DialogFragment() {
+ private var position = 0
+
+ private val settingsViewModel: SettingsViewModel by activityViewModels()
+
+ private lateinit var binding: DialogInputProfilesBinding
+
+ private lateinit var setting: InputProfileSetting
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ position = requireArguments().getInt(POSITION)
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ binding = DialogInputProfilesBinding.inflate(layoutInflater)
+
+ setting = settingsViewModel.clickedItem as InputProfileSetting
+ val options = mutableListOf<ProfileItem>().apply {
+ add(
+ NewProfileItem(
+ createNewProfile = {
+ NewInputProfileDialogFragment.newInstance(
+ settingsViewModel,
+ setting,
+ position
+ ).show(parentFragmentManager, NewInputProfileDialogFragment.TAG)
+ dismiss()
+ }
+ )
+ )
+
+ val onActionDismiss = {
+ settingsViewModel.setReloadListAndNotifyDataset(true)
+ dismiss()
+ }
+ setting.getProfileNames().forEach {
+ add(
+ ExistingProfileItem(
+ it,
+ deleteProfile = {
+ settingsViewModel.setShouldShowDeleteProfileDialog(it)
+ },
+ saveProfile = {
+ if (!setting.saveProfile(it)) {
+ Toast.makeText(
+ requireContext(),
+ R.string.failed_to_save_profile,
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ onActionDismiss.invoke()
+ },
+ loadProfile = {
+ if (!setting.loadProfile(it)) {
+ Toast.makeText(
+ requireContext(),
+ R.string.failed_to_load_profile,
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ onActionDismiss.invoke()
+ }
+ )
+ )
+ }
+ }
+ binding.listProfiles.apply {
+ layoutManager = LinearLayoutManager(requireContext())
+ adapter = InputProfileAdapter(options)
+ }
+
+ return MaterialAlertDialogBuilder(requireContext())
+ .setView(binding.root)
+ .create()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ settingsViewModel.shouldShowDeleteProfileDialog.collect(viewLifecycleOwner) {
+ if (it.isNotEmpty()) {
+ MessageDialogFragment.newInstance(
+ activity = requireActivity(),
+ titleId = R.string.delete_input_profile,
+ descriptionId = R.string.delete_input_profile_description,
+ positiveAction = {
+ setting.deleteProfile(it)
+ settingsViewModel.setReloadListAndNotifyDataset(true)
+ },
+ negativeAction = {},
+ negativeButtonTitleId = android.R.string.cancel
+ ).show(parentFragmentManager, MessageDialogFragment.TAG)
+ settingsViewModel.setShouldShowDeleteProfileDialog("")
+ dismiss()
+ }
+ }
+ }
+
+ companion object {
+ const val TAG = "InputProfileDialogFragment"
+
+ const val POSITION = "Position"
+
+ fun newInstance(
+ settingsViewModel: SettingsViewModel,
+ profileSetting: InputProfileSetting,
+ position: Int
+ ): InputProfileDialogFragment {
+ settingsViewModel.clickedItem = profileSetting
+
+ val args = Bundle()
+ args.putInt(POSITION, position)
+ val fragment = InputProfileDialogFragment()
+ fragment.arguments = args
+ return fragment
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/NewInputProfileDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/NewInputProfileDialogFragment.kt
new file mode 100644
index 000000000..6e52bea80
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/NewInputProfileDialogFragment.kt
@@ -0,0 +1,79 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.ui
+
+import android.app.Dialog
+import android.os.Bundle
+import android.widget.Toast
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.activityViewModels
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding
+import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting
+import org.yuzu.yuzu_emu.R
+
+class NewInputProfileDialogFragment : DialogFragment() {
+ private var position = 0
+
+ private val settingsViewModel: SettingsViewModel by activityViewModels()
+
+ private lateinit var binding: DialogEditTextBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ position = requireArguments().getInt(POSITION)
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ binding = DialogEditTextBinding.inflate(layoutInflater)
+
+ val setting = settingsViewModel.clickedItem as InputProfileSetting
+ return MaterialAlertDialogBuilder(requireContext())
+ .setTitle(R.string.enter_profile_name)
+ .setPositiveButton(android.R.string.ok) { _, _ ->
+ val profileName = binding.editText.text.toString()
+ if (!setting.isProfileNameValid(profileName)) {
+ Toast.makeText(
+ requireContext(),
+ R.string.invalid_profile_name,
+ Toast.LENGTH_SHORT
+ ).show()
+ return@setPositiveButton
+ }
+
+ if (!setting.createProfile(profileName)) {
+ Toast.makeText(
+ requireContext(),
+ R.string.profile_name_already_exists,
+ Toast.LENGTH_SHORT
+ ).show()
+ } else {
+ settingsViewModel.setAdapterItemChanged(position)
+ }
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .setView(binding.root)
+ .show()
+ }
+
+ companion object {
+ const val TAG = "NewInputProfileDialogFragment"
+
+ const val POSITION = "Position"
+
+ fun newInstance(
+ settingsViewModel: SettingsViewModel,
+ profileSetting: InputProfileSetting,
+ position: Int
+ ): NewInputProfileDialogFragment {
+ settingsViewModel.clickedItem = profileSetting
+
+ val args = Bundle()
+ args.putInt(POSITION, position)
+ val fragment = NewInputProfileDialogFragment()
+ fragment.arguments = args
+ return fragment
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
index 6f072241a..455b3b5ff 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
@@ -13,21 +13,16 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.navArgs
import com.google.android.material.color.MaterialColors
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.NativeLibrary
import java.io.IOException
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
+import org.yuzu.yuzu_emu.features.input.NativeInput
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
-import org.yuzu.yuzu_emu.model.SettingsViewModel
import org.yuzu.yuzu_emu.utils.*
class SettingsActivity : AppCompatActivity() {
@@ -70,39 +65,23 @@ class SettingsActivity : AppCompatActivity() {
)
}
- lifecycleScope.apply {
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- settingsViewModel.shouldRecreate.collectLatest {
- if (it) {
- settingsViewModel.setShouldRecreate(false)
- recreate()
- }
- }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- settingsViewModel.shouldNavigateBack.collectLatest {
- if (it) {
- settingsViewModel.setShouldNavigateBack(false)
- navigateBack()
- }
- }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- settingsViewModel.shouldShowResetSettingsDialog.collectLatest {
- if (it) {
- settingsViewModel.setShouldShowResetSettingsDialog(false)
- ResetSettingsDialogFragment().show(
- supportFragmentManager,
- ResetSettingsDialogFragment.TAG
- )
- }
- }
- }
+ settingsViewModel.shouldRecreate.collect(
+ this,
+ resetState = { settingsViewModel.setShouldRecreate(false) }
+ ) { if (it) recreate() }
+ settingsViewModel.shouldNavigateBack.collect(
+ this,
+ resetState = { settingsViewModel.setShouldNavigateBack(false) }
+ ) { if (it) navigateBack() }
+ settingsViewModel.shouldShowResetSettingsDialog.collect(
+ this,
+ resetState = { settingsViewModel.setShouldShowResetSettingsDialog(false) }
+ ) {
+ if (it) {
+ ResetSettingsDialogFragment().show(
+ supportFragmentManager,
+ ResetSettingsDialogFragment.TAG
+ )
}
}
@@ -137,6 +116,7 @@ class SettingsActivity : AppCompatActivity() {
super.onStop()
Log.info("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
if (isFinishing) {
+ NativeInput.reloadInputDevices()
NativeLibrary.applySettings()
if (args.game == null) {
NativeConfig.saveGlobalConfig()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index be9b3031b..45c8faa10 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -8,12 +8,11 @@ import android.icu.util.Calendar
import android.icu.util.TimeZone
import android.text.format.DateFormat
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
+import android.widget.PopupMenu
import androidx.fragment.app.Fragment
-import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
@@ -21,16 +20,18 @@ import androidx.recyclerview.widget.ListAdapter
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
-import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.SettingsNavigationDirections
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
+import org.yuzu.yuzu_emu.databinding.ListItemSettingInputBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
+import org.yuzu.yuzu_emu.features.input.NativeInput
+import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
+import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
-import org.yuzu.yuzu_emu.fragments.SettingsDialogFragment
-import org.yuzu.yuzu_emu.model.SettingsViewModel
+import org.yuzu.yuzu_emu.utils.ParamPackage
class SettingsAdapter(
private val fragment: Fragment,
@@ -41,19 +42,6 @@ class SettingsAdapter(
private val settingsViewModel: SettingsViewModel
get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java]
- init {
- fragment.viewLifecycleOwner.lifecycleScope.launch {
- fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
- settingsViewModel.adapterItemChanged.collect {
- if (it != -1) {
- notifyItemChanged(it)
- settingsViewModel.setAdapterItemChanged(-1)
- }
- }
- }
- }
- }
-
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
@@ -85,8 +73,19 @@ class SettingsAdapter(
RunnableViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
+ SettingsItem.TYPE_INPUT -> {
+ InputViewHolder(ListItemSettingInputBinding.inflate(inflater), this)
+ }
+
+ SettingsItem.TYPE_INT_SINGLE_CHOICE -> {
+ SingleChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
+ }
+
+ SettingsItem.TYPE_INPUT_PROFILE -> {
+ InputProfileViewHolder(ListItemSettingBinding.inflate(inflater), this)
+ }
+
else -> {
- // TODO: Create an error view since we can't return null now
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
}
}
@@ -126,6 +125,15 @@ class SettingsAdapter(
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
}
+ fun onIntSingleChoiceClick(item: IntSingleChoiceSetting, position: Int) {
+ SettingsDialogFragment.newInstance(
+ settingsViewModel,
+ item,
+ SettingsItem.TYPE_INT_SINGLE_CHOICE,
+ position
+ ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
+ }
+
fun onDateTimeClick(item: DateTimeSetting, position: Int) {
val storedTime = item.getValue() * 1000
@@ -185,6 +193,205 @@ class SettingsAdapter(
fragment.view?.findNavController()?.navigate(action)
}
+ fun onInputProfileClick(item: InputProfileSetting, position: Int) {
+ InputProfileDialogFragment.newInstance(
+ settingsViewModel,
+ item,
+ position
+ ).show(fragment.childFragmentManager, InputProfileDialogFragment.TAG)
+ }
+
+ fun onInputClick(item: InputSetting, position: Int) {
+ InputDialogFragment.newInstance(
+ settingsViewModel,
+ item,
+ position
+ ).show(fragment.childFragmentManager, InputDialogFragment.TAG)
+ }
+
+ fun onInputOptionsClick(anchor: View, item: InputSetting, position: Int) {
+ val popup = PopupMenu(context, anchor)
+ popup.menuInflater.inflate(R.menu.menu_input_options, popup.menu)
+
+ popup.menu.apply {
+ val invertAxis = findItem(R.id.invert_axis)
+ val invertButton = findItem(R.id.invert_button)
+ val toggleButton = findItem(R.id.toggle_button)
+ val turboButton = findItem(R.id.turbo_button)
+ val setThreshold = findItem(R.id.set_threshold)
+ val toggleAxis = findItem(R.id.toggle_axis)
+ when (item) {
+ is AnalogInputSetting -> {
+ val params = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog)
+
+ invertAxis.isVisible = true
+ invertAxis.isCheckable = true
+ invertAxis.isChecked = when (item.analogDirection) {
+ AnalogDirection.Left, AnalogDirection.Right -> {
+ params.get("invert_x", "+") == "-"
+ }
+
+ AnalogDirection.Up, AnalogDirection.Down -> {
+ params.get("invert_y", "+") == "-"
+ }
+ }
+ invertAxis.setOnMenuItemClickListener {
+ if (item.analogDirection == AnalogDirection.Left ||
+ item.analogDirection == AnalogDirection.Right
+ ) {
+ val invertValue = params.get("invert_x", "+") == "-"
+ val invertString = if (invertValue) "+" else "-"
+ params.set("invert_x", invertString)
+ } else if (
+ item.analogDirection == AnalogDirection.Up ||
+ item.analogDirection == AnalogDirection.Down
+ ) {
+ val invertValue = params.get("invert_y", "+") == "-"
+ val invertString = if (invertValue) "+" else "-"
+ params.set("invert_y", invertString)
+ }
+ true
+ }
+
+ popup.setOnDismissListener {
+ NativeInput.setStickParam(item.playerIndex, item.nativeAnalog, params)
+ settingsViewModel.setDatasetChanged(true)
+ }
+ }
+
+ is ButtonInputSetting -> {
+ val params = NativeInput.getButtonParam(item.playerIndex, item.nativeButton)
+ if (params.has("code") || params.has("button") || params.has("hat")) {
+ val buttonInvert = params.get("inverted", false)
+ invertButton.isVisible = true
+ invertButton.isCheckable = true
+ invertButton.isChecked = buttonInvert
+ invertButton.setOnMenuItemClickListener {
+ params.set("inverted", !buttonInvert)
+ true
+ }
+
+ val toggle = params.get("toggle", false)
+ toggleButton.isVisible = true
+ toggleButton.isCheckable = true
+ toggleButton.isChecked = toggle
+ toggleButton.setOnMenuItemClickListener {
+ params.set("toggle", !toggle)
+ true
+ }
+
+ val turbo = params.get("turbo", false)
+ turboButton.isVisible = true
+ turboButton.isCheckable = true
+ turboButton.isChecked = turbo
+ turboButton.setOnMenuItemClickListener {
+ params.set("turbo", !turbo)
+ true
+ }
+ } else if (params.has("axis")) {
+ val axisInvert = params.get("invert", "+") == "-"
+ invertAxis.isVisible = true
+ invertAxis.isCheckable = true
+ invertAxis.isChecked = axisInvert
+ invertAxis.setOnMenuItemClickListener {
+ params.set("invert", if (!axisInvert) "-" else "+")
+ true
+ }
+
+ val buttonInvert = params.get("inverted", false)
+ invertButton.isVisible = true
+ invertButton.isCheckable = true
+ invertButton.isChecked = buttonInvert
+ invertButton.setOnMenuItemClickListener {
+ params.set("inverted", !buttonInvert)
+ true
+ }
+
+ setThreshold.isVisible = true
+ val thresholdSetting = object : AbstractIntSetting {
+ override val key = ""
+
+ override fun getInt(needsGlobal: Boolean): Int =
+ (params.get("threshold", 0.5f) * 100).toInt()
+
+ override fun setInt(value: Int) {
+ params.set("threshold", value.toFloat() / 100)
+ NativeInput.setButtonParam(
+ item.playerIndex,
+ item.nativeButton,
+ params
+ )
+ }
+
+ override val defaultValue = 50
+
+ override fun getValueAsString(needsGlobal: Boolean): String =
+ getInt(needsGlobal).toString()
+
+ override fun reset() = setInt(defaultValue)
+ }
+ setThreshold.setOnMenuItemClickListener {
+ onSliderClick(
+ SliderSetting(thresholdSetting, R.string.set_threshold),
+ position
+ )
+ true
+ }
+
+ val axisToggle = params.get("toggle", false)
+ toggleAxis.isVisible = true
+ toggleAxis.isCheckable = true
+ toggleAxis.isChecked = axisToggle
+ toggleAxis.setOnMenuItemClickListener {
+ params.set("toggle", !axisToggle)
+ true
+ }
+ }
+
+ popup.setOnDismissListener {
+ NativeInput.setButtonParam(item.playerIndex, item.nativeButton, params)
+ settingsViewModel.setAdapterItemChanged(position)
+ }
+ }
+
+ is ModifierInputSetting -> {
+ val stickParams = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog)
+ val modifierParams = ParamPackage(stickParams.get("modifier", ""))
+
+ val invert = modifierParams.get("inverted", false)
+ invertButton.isVisible = true
+ invertButton.isCheckable = true
+ invertButton.isChecked = invert
+ invertButton.setOnMenuItemClickListener {
+ modifierParams.set("inverted", !invert)
+ stickParams.set("modifier", modifierParams.serialize())
+ true
+ }
+
+ val toggle = modifierParams.get("toggle", false)
+ toggleButton.isVisible = true
+ toggleButton.isCheckable = true
+ toggleButton.isChecked = toggle
+ toggleButton.setOnMenuItemClickListener {
+ modifierParams.set("toggle", !toggle)
+ stickParams.set("modifier", modifierParams.serialize())
+ true
+ }
+
+ popup.setOnDismissListener {
+ NativeInput.setStickParam(
+ item.playerIndex,
+ item.nativeAnalog,
+ stickParams
+ )
+ settingsViewModel.setAdapterItemChanged(position)
+ }
+ }
+ }
+ }
+ popup.show()
+ }
+
fun onLongClick(item: SettingsItem, position: Int): Boolean {
SettingsDialogFragment.newInstance(
settingsViewModel,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt
index 60e029f34..a81ff6b1a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-package org.yuzu.yuzu_emu.fragments
+package org.yuzu.yuzu_emu.features.settings.ui
import android.app.Dialog
import android.content.DialogInterface
@@ -11,19 +11,21 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider
-import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
+import org.yuzu.yuzu_emu.features.input.NativeInput
+import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
+import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.ButtonInputSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.IntSingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
-import org.yuzu.yuzu_emu.model.SettingsViewModel
+import org.yuzu.yuzu_emu.utils.ParamPackage
+import org.yuzu.yuzu_emu.utils.collect
class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
private var type = 0
@@ -50,8 +52,49 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.reset_setting_confirmation)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
- settingsViewModel.clickedItem!!.setting.reset()
- settingsViewModel.setAdapterItemChanged(position)
+ when (val item = settingsViewModel.clickedItem) {
+ is AnalogInputSetting -> {
+ val stickParam = NativeInput.getStickParam(
+ item.playerIndex,
+ item.nativeAnalog
+ )
+ if (stickParam.get("engine", "") == "analog_from_button") {
+ when (item.analogDirection) {
+ AnalogDirection.Up -> stickParam.erase("up")
+ AnalogDirection.Down -> stickParam.erase("down")
+ AnalogDirection.Left -> stickParam.erase("left")
+ AnalogDirection.Right -> stickParam.erase("right")
+ }
+ NativeInput.setStickParam(
+ item.playerIndex,
+ item.nativeAnalog,
+ stickParam
+ )
+ settingsViewModel.setAdapterItemChanged(position)
+ } else {
+ NativeInput.setStickParam(
+ item.playerIndex,
+ item.nativeAnalog,
+ ParamPackage()
+ )
+ settingsViewModel.setDatasetChanged(true)
+ }
+ }
+
+ is ButtonInputSetting -> {
+ NativeInput.setButtonParam(
+ item.playerIndex,
+ item.nativeButton,
+ ParamPackage()
+ )
+ settingsViewModel.setAdapterItemChanged(position)
+ }
+
+ else -> {
+ settingsViewModel.clickedItem!!.setting.reset()
+ settingsViewModel.setAdapterItemChanged(position)
+ }
+ }
}
.setNegativeButton(android.R.string.cancel, null)
.create()
@@ -61,7 +104,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
val item = settingsViewModel.clickedItem as SingleChoiceSetting
val value = getSelectionForSingleChoiceValue(item)
MaterialAlertDialogBuilder(requireContext())
- .setTitle(item.nameId)
+ .setTitle(item.title)
.setSingleChoiceItems(item.choicesId, value, this)
.create()
}
@@ -81,7 +124,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
}
MaterialAlertDialogBuilder(requireContext())
- .setTitle(item.nameId)
+ .setTitle(item.title)
.setView(sliderBinding.root)
.setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
@@ -91,8 +134,16 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
val item = settingsViewModel.clickedItem as StringSingleChoiceSetting
MaterialAlertDialogBuilder(requireContext())
- .setTitle(item.nameId)
- .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
+ .setTitle(item.title)
+ .setSingleChoiceItems(item.choices, item.selectedValueIndex, this)
+ .create()
+ }
+
+ SettingsItem.TYPE_INT_SINGLE_CHOICE -> {
+ val item = settingsViewModel.clickedItem as IntSingleChoiceSetting
+ MaterialAlertDialogBuilder(requireContext())
+ .setTitle(item.title)
+ .setSingleChoiceItems(item.choices, item.selectedValueIndex, this)
.create()
}
@@ -115,17 +166,11 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
super.onViewCreated(view, savedInstanceState)
when (type) {
SettingsItem.TYPE_SLIDER -> {
- viewLifecycleOwner.lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- settingsViewModel.sliderTextValue.collect {
- sliderBinding.textValue.text = it
- }
- }
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- settingsViewModel.sliderProgress.collect {
- sliderBinding.slider.value = it.toFloat()
- }
- }
+ settingsViewModel.sliderTextValue.collect(viewLifecycleOwner) {
+ sliderBinding.textValue.text = it
+ }
+ settingsViewModel.sliderProgress.collect(viewLifecycleOwner) {
+ sliderBinding.slider.value = it.toFloat()
}
}
}
@@ -145,6 +190,12 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
scSetting.setSelectedValue(value)
}
+ is IntSingleChoiceSetting -> {
+ val scSetting = settingsViewModel.clickedItem as IntSingleChoiceSetting
+ val value = scSetting.getValueAt(which)
+ scSetting.setSelectedValue(value)
+ }
+
is SliderSetting -> {
val sliderSetting = settingsViewModel.clickedItem as SliderSetting
sliderSetting.setSelectedValue(settingsViewModel.sliderProgress.value)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
index 6f6e7be10..ec16f16c4 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
@@ -13,20 +13,17 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
+import org.yuzu.yuzu_emu.features.input.NativeInput
import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.model.SettingsViewModel
+import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
+import org.yuzu.yuzu_emu.utils.collect
class SettingsFragment : Fragment() {
private lateinit var presenter: SettingsFragmentPresenter
@@ -45,6 +42,12 @@ class SettingsFragment : Fragment() {
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+
+ val playerIndex = getPlayerIndex()
+ if (playerIndex != -1) {
+ NativeInput.loadInputProfiles()
+ NativeInput.reloadInputDevices()
+ }
}
override fun onCreateView(
@@ -56,9 +59,9 @@ class SettingsFragment : Fragment() {
return binding.root
}
- // This is using the correct scope, lint is just acting up
- @SuppressLint("UnsafeRepeatOnLifecycleDetector")
+ @SuppressLint("NotifyDataSetChanged")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
settingsAdapter = SettingsAdapter(this, requireContext())
presenter = SettingsFragmentPresenter(
settingsViewModel,
@@ -71,7 +74,17 @@ class SettingsFragment : Fragment() {
) {
args.game!!.title
} else {
- getString(args.menuTag.titleId)
+ when (args.menuTag) {
+ Settings.MenuTag.SECTION_INPUT_PLAYER_ONE -> Settings.getPlayerString(1)
+ Settings.MenuTag.SECTION_INPUT_PLAYER_TWO -> Settings.getPlayerString(2)
+ Settings.MenuTag.SECTION_INPUT_PLAYER_THREE -> Settings.getPlayerString(3)
+ Settings.MenuTag.SECTION_INPUT_PLAYER_FOUR -> Settings.getPlayerString(4)
+ Settings.MenuTag.SECTION_INPUT_PLAYER_FIVE -> Settings.getPlayerString(5)
+ Settings.MenuTag.SECTION_INPUT_PLAYER_SIX -> Settings.getPlayerString(6)
+ Settings.MenuTag.SECTION_INPUT_PLAYER_SEVEN -> Settings.getPlayerString(7)
+ Settings.MenuTag.SECTION_INPUT_PLAYER_EIGHT -> Settings.getPlayerString(8)
+ else -> getString(args.menuTag.titleId)
+ }
}
binding.listSettings.apply {
adapter = settingsAdapter
@@ -82,16 +95,37 @@ class SettingsFragment : Fragment() {
settingsViewModel.setShouldNavigateBack(true)
}
- viewLifecycleOwner.lifecycleScope.apply {
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- settingsViewModel.shouldReloadSettingsList.collectLatest {
- if (it) {
- settingsViewModel.setShouldReloadSettingsList(false)
- presenter.loadSettingsList()
- }
- }
- }
+ settingsViewModel.shouldReloadSettingsList.collect(
+ viewLifecycleOwner,
+ resetState = { settingsViewModel.setShouldReloadSettingsList(false) }
+ ) { if (it) presenter.loadSettingsList() }
+ settingsViewModel.adapterItemChanged.collect(
+ viewLifecycleOwner,
+ resetState = { settingsViewModel.setAdapterItemChanged(-1) }
+ ) { if (it != -1) settingsAdapter?.notifyItemChanged(it) }
+ settingsViewModel.datasetChanged.collect(
+ viewLifecycleOwner,
+ resetState = { settingsViewModel.setDatasetChanged(false) }
+ ) { if (it) settingsAdapter?.notifyDataSetChanged() }
+ settingsViewModel.reloadListAndNotifyDataset.collect(
+ viewLifecycleOwner,
+ resetState = { settingsViewModel.setReloadListAndNotifyDataset(false) }
+ ) { if (it) presenter.loadSettingsList(true) }
+ settingsViewModel.shouldShowResetInputDialog.collect(
+ viewLifecycleOwner,
+ resetState = { settingsViewModel.setShouldShowResetInputDialog(false) }
+ ) {
+ if (it) {
+ MessageDialogFragment.newInstance(
+ activity = requireActivity(),
+ titleId = R.string.reset_mapping,
+ descriptionId = R.string.reset_mapping_description,
+ positiveAction = {
+ NativeInput.resetControllerMappings(getPlayerIndex())
+ settingsViewModel.setReloadListAndNotifyDataset(true)
+ },
+ negativeAction = {}
+ ).show(parentFragmentManager, MessageDialogFragment.TAG)
}
}
@@ -115,6 +149,19 @@ class SettingsFragment : Fragment() {
setInsets()
}
+ private fun getPlayerIndex(): Int =
+ when (args.menuTag) {
+ Settings.MenuTag.SECTION_INPUT_PLAYER_ONE -> 0
+ Settings.MenuTag.SECTION_INPUT_PLAYER_TWO -> 1
+ Settings.MenuTag.SECTION_INPUT_PLAYER_THREE -> 2
+ Settings.MenuTag.SECTION_INPUT_PLAYER_FOUR -> 3
+ Settings.MenuTag.SECTION_INPUT_PLAYER_FIVE -> 4
+ Settings.MenuTag.SECTION_INPUT_PLAYER_SIX -> 5
+ Settings.MenuTag.SECTION_INPUT_PLAYER_SEVEN -> 6
+ Settings.MenuTag.SECTION_INPUT_PLAYER_EIGHT -> 7
+ else -> -1
+ }
+
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index db1a58147..e491c29a2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -3,11 +3,17 @@
package org.yuzu.yuzu_emu.features.settings.ui
+import android.annotation.SuppressLint
import android.os.Build
import android.widget.Toast
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.features.input.NativeInput
+import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
+import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
+import org.yuzu.yuzu_emu.features.input.model.NativeButton
+import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
@@ -15,18 +21,21 @@ import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.view.*
-import org.yuzu.yuzu_emu.model.SettingsViewModel
+import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.NativeConfig
class SettingsFragmentPresenter(
private val settingsViewModel: SettingsViewModel,
private val adapter: SettingsAdapter,
- private var menuTag: Settings.MenuTag
+ private var menuTag: MenuTag
) {
private var settingsList = ArrayList<SettingsItem>()
+ private val context get() = YuzuApplication.appContext
+
// Extension for altering settings list based on each setting's properties
fun ArrayList<SettingsItem>.add(key: String) {
val item = SettingsItem.settingsItems[key]!!
@@ -53,73 +62,90 @@ class SettingsFragmentPresenter(
add(item)
}
+ // Allows you to show/hide abstract settings based on the paired setting key
+ fun ArrayList<SettingsItem>.addAbstract(item: SettingsItem) {
+ val pairedSettingKey = item.setting.pairedSettingKey
+ if (pairedSettingKey.isNotEmpty()) {
+ val pairedSettingsItem =
+ this.firstOrNull { it.setting.key == pairedSettingKey } ?: return
+ val pairedSetting = pairedSettingsItem.setting as AbstractBooleanSetting
+ if (!pairedSetting.getBoolean(!NativeConfig.isPerGameConfigLoaded())) return
+ }
+ add(item)
+ }
+
fun onViewCreated() {
loadSettingsList()
}
- fun loadSettingsList() {
+ @SuppressLint("NotifyDataSetChanged")
+ fun loadSettingsList(notifyDataSetChanged: Boolean = false) {
val sl = ArrayList<SettingsItem>()
when (menuTag) {
- Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl)
- Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
- Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
- Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
- Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl)
- Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
- else -> {
- val context = YuzuApplication.appContext
- Toast.makeText(
- context,
- context.getString(R.string.unimplemented_menu),
- Toast.LENGTH_SHORT
- ).show()
- return
- }
+ MenuTag.SECTION_ROOT -> addConfigSettings(sl)
+ MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
+ MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
+ MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
+ MenuTag.SECTION_INPUT -> addInputSettings(sl)
+ MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0)
+ MenuTag.SECTION_INPUT_PLAYER_TWO -> addInputPlayer(sl, 1)
+ MenuTag.SECTION_INPUT_PLAYER_THREE -> addInputPlayer(sl, 2)
+ MenuTag.SECTION_INPUT_PLAYER_FOUR -> addInputPlayer(sl, 3)
+ MenuTag.SECTION_INPUT_PLAYER_FIVE -> addInputPlayer(sl, 4)
+ MenuTag.SECTION_INPUT_PLAYER_SIX -> addInputPlayer(sl, 5)
+ MenuTag.SECTION_INPUT_PLAYER_SEVEN -> addInputPlayer(sl, 6)
+ MenuTag.SECTION_INPUT_PLAYER_EIGHT -> addInputPlayer(sl, 7)
+ MenuTag.SECTION_THEME -> addThemeSettings(sl)
+ MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
}
settingsList = sl
- adapter.submitList(settingsList)
+ adapter.submitList(settingsList) {
+ if (notifyDataSetChanged) {
+ adapter.notifyDataSetChanged()
+ }
+ }
}
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
sl.apply {
add(
SubmenuSetting(
- R.string.preferences_system,
- R.string.preferences_system_description,
- R.drawable.ic_system_settings,
- Settings.MenuTag.SECTION_SYSTEM
+ titleId = R.string.preferences_system,
+ descriptionId = R.string.preferences_system_description,
+ iconId = R.drawable.ic_system_settings,
+ menuKey = MenuTag.SECTION_SYSTEM
)
)
add(
SubmenuSetting(
- R.string.preferences_graphics,
- R.string.preferences_graphics_description,
- R.drawable.ic_graphics,
- Settings.MenuTag.SECTION_RENDERER
+ titleId = R.string.preferences_graphics,
+ descriptionId = R.string.preferences_graphics_description,
+ iconId = R.drawable.ic_graphics,
+ menuKey = MenuTag.SECTION_RENDERER
)
)
add(
SubmenuSetting(
- R.string.preferences_audio,
- R.string.preferences_audio_description,
- R.drawable.ic_audio,
- Settings.MenuTag.SECTION_AUDIO
+ titleId = R.string.preferences_audio,
+ descriptionId = R.string.preferences_audio_description,
+ iconId = R.drawable.ic_audio,
+ menuKey = MenuTag.SECTION_AUDIO
)
)
add(
SubmenuSetting(
- R.string.preferences_debug,
- R.string.preferences_debug_description,
- R.drawable.ic_code,
- Settings.MenuTag.SECTION_DEBUG
+ titleId = R.string.preferences_debug,
+ descriptionId = R.string.preferences_debug_description,
+ iconId = R.drawable.ic_code,
+ menuKey = MenuTag.SECTION_DEBUG
)
)
add(
RunnableSetting(
- R.string.reset_to_default,
- R.string.reset_to_default_description,
- false,
- R.drawable.ic_restore
+ titleId = R.string.reset_to_default,
+ descriptionId = R.string.reset_to_default_description,
+ isRunnable = !NativeLibrary.isRunning(),
+ iconId = R.drawable.ic_restore
) { settingsViewModel.setShouldShowResetSettingsDialog(true) }
)
}
@@ -164,6 +190,671 @@ class SettingsFragmentPresenter(
}
}
+ private fun addInputSettings(sl: ArrayList<SettingsItem>) {
+ settingsViewModel.currentDevice = 0
+
+ if (NativeConfig.isPerGameConfigLoaded()) {
+ NativeInput.loadInputProfiles()
+ val profiles = NativeInput.getInputProfileNames().toMutableList()
+ profiles.add(0, "")
+ val prettyProfiles = profiles.toTypedArray()
+ prettyProfiles[0] =
+ context.getString(R.string.use_global_input_configuration)
+ sl.apply {
+ for (i in 0 until 8) {
+ add(
+ IntSingleChoiceSetting(
+ getPerGameProfileSetting(profiles, i),
+ titleString = getPlayerProfileString(i + 1),
+ choices = prettyProfiles,
+ values = IntArray(profiles.size) { it }.toTypedArray()
+ )
+ )
+ }
+ }
+ return
+ }
+
+ val getConnectedIcon: (Int) -> Int = { playerIndex: Int ->
+ if (NativeInput.getIsConnected(playerIndex)) {
+ R.drawable.ic_controller
+ } else {
+ R.drawable.ic_controller_disconnected
+ }
+ }
+
+ val inputSettings = NativeConfig.getInputSettings(true)
+ sl.apply {
+ add(
+ SubmenuSetting(
+ titleString = Settings.getPlayerString(1),
+ descriptionString = inputSettings[0].profileName,
+ menuKey = MenuTag.SECTION_INPUT_PLAYER_ONE,
+ iconId = getConnectedIcon(0)
+ )
+ )
+ add(
+ SubmenuSetting(
+ titleString = Settings.getPlayerString(2),
+ descriptionString = inputSettings[1].profileName,
+ menuKey = MenuTag.SECTION_INPUT_PLAYER_TWO,
+ iconId = getConnectedIcon(1)
+ )
+ )
+ add(
+ SubmenuSetting(
+ titleString = Settings.getPlayerString(3),
+ descriptionString = inputSettings[2].profileName,
+ menuKey = MenuTag.SECTION_INPUT_PLAYER_THREE,
+ iconId = getConnectedIcon(2)
+ )
+ )
+ add(
+ SubmenuSetting(
+ titleString = Settings.getPlayerString(4),
+ descriptionString = inputSettings[3].profileName,
+ menuKey = MenuTag.SECTION_INPUT_PLAYER_FOUR,
+ iconId = getConnectedIcon(3)
+ )
+ )
+ add(
+ SubmenuSetting(
+ titleString = Settings.getPlayerString(5),
+ descriptionString = inputSettings[4].profileName,
+ menuKey = MenuTag.SECTION_INPUT_PLAYER_FIVE,
+ iconId = getConnectedIcon(4)
+ )
+ )
+ add(
+ SubmenuSetting(
+ titleString = Settings.getPlayerString(6),
+ descriptionString = inputSettings[5].profileName,
+ menuKey = MenuTag.SECTION_INPUT_PLAYER_SIX,
+ iconId = getConnectedIcon(5)
+ )
+ )
+ add(
+ SubmenuSetting(
+ titleString = Settings.getPlayerString(7),
+ descriptionString = inputSettings[6].profileName,
+ menuKey = MenuTag.SECTION_INPUT_PLAYER_SEVEN,
+ iconId = getConnectedIcon(6)
+ )
+ )
+ add(
+ SubmenuSetting(
+ titleString = Settings.getPlayerString(8),
+ descriptionString = inputSettings[7].profileName,
+ menuKey = MenuTag.SECTION_INPUT_PLAYER_EIGHT,
+ iconId = getConnectedIcon(7)
+ )
+ )
+ }
+ }
+
+ private fun getPlayerProfileString(player: Int): String =
+ context.getString(R.string.player_num_profile, player)
+
+ private fun getPerGameProfileSetting(
+ profiles: List<String>,
+ playerIndex: Int
+ ): AbstractIntSetting {
+ return object : AbstractIntSetting {
+ private val players
+ get() = NativeConfig.getInputSettings(false)
+
+ override val key = ""
+
+ override fun getInt(needsGlobal: Boolean): Int {
+ val currentProfile = players[playerIndex].profileName
+ profiles.forEachIndexed { i, profile ->
+ if (profile == currentProfile) {
+ return i
+ }
+ }
+ return 0
+ }
+
+ override fun setInt(value: Int) {
+ NativeInput.loadPerGameConfiguration(playerIndex, value, profiles[value])
+ NativeInput.connectControllers(playerIndex)
+ NativeConfig.saveControlPlayerValues()
+ }
+
+ override val defaultValue = 0
+
+ override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
+
+ override fun reset() = setInt(defaultValue)
+
+ override var global = true
+
+ override val isRuntimeModifiable = true
+
+ override val isSaveable = true
+ }
+ }
+
+ private fun addInputPlayer(sl: ArrayList<SettingsItem>, playerIndex: Int) {
+ sl.apply {
+ val connectedSetting = object : AbstractBooleanSetting {
+ override val key = "connected"
+
+ override fun getBoolean(needsGlobal: Boolean): Boolean =
+ NativeInput.getIsConnected(playerIndex)
+
+ override fun setBoolean(value: Boolean) =
+ NativeInput.connectControllers(playerIndex, value)
+
+ override val defaultValue = playerIndex == 0
+
+ override fun getValueAsString(needsGlobal: Boolean): String =
+ getBoolean(needsGlobal).toString()
+
+ override fun reset() = setBoolean(defaultValue)
+ }
+ add(SwitchSetting(connectedSetting, R.string.connected))
+
+ val styleTags = NativeInput.getSupportedStyleTags(playerIndex)
+ val npadType = object : AbstractIntSetting {
+ override val key = "npad_type"
+ override fun getInt(needsGlobal: Boolean): Int {
+ val styleIndex = NativeInput.getStyleIndex(playerIndex)
+ return styleTags.indexOfFirst { it == styleIndex }
+ }
+
+ override fun setInt(value: Int) {
+ NativeInput.setStyleIndex(playerIndex, styleTags[value])
+ settingsViewModel.setReloadListAndNotifyDataset(true)
+ }
+
+ override val defaultValue = NpadStyleIndex.Fullkey.int
+ override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
+ override fun reset() = setInt(defaultValue)
+ override val pairedSettingKey: String = "connected"
+ }
+ addAbstract(
+ IntSingleChoiceSetting(
+ npadType,
+ titleId = R.string.controller_type,
+ choices = styleTags.map { context.getString(it.nameId) }
+ .toTypedArray(),
+ values = IntArray(styleTags.size) { it }.toTypedArray()
+ )
+ )
+
+ InputHandler.updateControllerData()
+
+ val autoMappingSetting = object : AbstractIntSetting {
+ override val key = "auto_mapping_device"
+
+ override fun getInt(needsGlobal: Boolean): Int = -1
+
+ override fun setInt(value: Int) {
+ val registeredController = InputHandler.registeredControllers[value + 1]
+ val displayName = registeredController.get(
+ "display",
+ context.getString(R.string.unknown)
+ )
+ NativeInput.updateMappingsWithDefault(
+ playerIndex,
+ registeredController,
+ displayName
+ )
+ Toast.makeText(
+ context,
+ context.getString(R.string.attempted_auto_map, displayName),
+ Toast.LENGTH_SHORT
+ ).show()
+ settingsViewModel.setReloadListAndNotifyDataset(true)
+ }
+
+ override val defaultValue = -1
+
+ override fun getValueAsString(needsGlobal: Boolean) = getInt().toString()
+
+ override fun reset() = setInt(defaultValue)
+
+ override val isRuntimeModifiable: Boolean = true
+ }
+
+ val unknownString = context.getString(R.string.unknown)
+ val prettyAutoMappingControllerList = InputHandler.registeredControllers.mapNotNull {
+ val port = it.get("port", -1)
+ return@mapNotNull if (port == 100 || port == -1) {
+ null
+ } else {
+ it.get("display", unknownString)
+ }
+ }.toTypedArray()
+ add(
+ IntSingleChoiceSetting(
+ autoMappingSetting,
+ titleId = R.string.auto_map,
+ descriptionId = R.string.auto_map_description,
+ choices = prettyAutoMappingControllerList,
+ values = IntArray(prettyAutoMappingControllerList.size) { it }.toTypedArray()
+ )
+ )
+
+ val mappingFilterSetting = object : AbstractIntSetting {
+ override val key = "mapping_filter"
+
+ override fun getInt(needsGlobal: Boolean): Int = settingsViewModel.currentDevice
+
+ override fun setInt(value: Int) {
+ settingsViewModel.currentDevice = value
+ }
+
+ override val defaultValue = 0
+
+ override fun getValueAsString(needsGlobal: Boolean) = getInt().toString()
+
+ override fun reset() = setInt(defaultValue)
+
+ override val isRuntimeModifiable: Boolean = true
+ }
+
+ val prettyControllerList = InputHandler.registeredControllers.mapNotNull {
+ return@mapNotNull if (it.get("port", 0) == 100) {
+ null
+ } else {
+ it.get("display", unknownString)
+ }
+ }.toTypedArray()
+ add(
+ IntSingleChoiceSetting(
+ mappingFilterSetting,
+ titleId = R.string.input_mapping_filter,
+ descriptionId = R.string.input_mapping_filter_description,
+ choices = prettyControllerList,
+ values = IntArray(prettyControllerList.size) { it }.toTypedArray()
+ )
+ )
+
+ add(InputProfileSetting(playerIndex))
+ add(
+ RunnableSetting(titleId = R.string.reset_to_default, isRunnable = true) {
+ settingsViewModel.setShouldShowResetInputDialog(true)
+ }
+ )
+
+ val styleIndex = NativeInput.getStyleIndex(playerIndex)
+
+ // Buttons
+ when (styleIndex) {
+ NpadStyleIndex.Fullkey,
+ NpadStyleIndex.Handheld,
+ NpadStyleIndex.JoyconDual -> {
+ add(HeaderSetting(R.string.buttons))
+ add(ButtonInputSetting(playerIndex, NativeButton.A, R.string.button_a))
+ add(ButtonInputSetting(playerIndex, NativeButton.B, R.string.button_b))
+ add(ButtonInputSetting(playerIndex, NativeButton.X, R.string.button_x))
+ add(ButtonInputSetting(playerIndex, NativeButton.Y, R.string.button_y))
+ add(ButtonInputSetting(playerIndex, NativeButton.Plus, R.string.button_plus))
+ add(ButtonInputSetting(playerIndex, NativeButton.Minus, R.string.button_minus))
+ add(ButtonInputSetting(playerIndex, NativeButton.Home, R.string.button_home))
+ add(
+ ButtonInputSetting(
+ playerIndex,
+ NativeButton.Capture,
+ R.string.button_capture
+ )
+ )
+ }
+
+ NpadStyleIndex.JoyconLeft -> {
+ add(HeaderSetting(R.string.buttons))
+ add(ButtonInputSetting(playerIndex, NativeButton.Minus, R.string.button_minus))
+ add(
+ ButtonInputSetting(
+ playerIndex,
+ NativeButton.Capture,
+ R.string.button_capture
+ )
+ )
+ }
+
+ NpadStyleIndex.JoyconRight -> {
+ add(HeaderSetting(R.string.buttons))
+ add(ButtonInputSetting(playerIndex, NativeButton.A, R.string.button_a))
+ add(ButtonInputSetting(playerIndex, NativeButton.B, R.string.button_b))
+ add(ButtonInputSetting(playerIndex, NativeButton.X, R.string.button_x))
+ add(ButtonInputSetting(playerIndex, NativeButton.Y, R.string.button_y))
+ add(ButtonInputSetting(playerIndex, NativeButton.Plus, R.string.button_plus))
+ add(ButtonInputSetting(playerIndex, NativeButton.Home, R.string.button_home))
+ }
+
+ NpadStyleIndex.GameCube -> {
+ add(HeaderSetting(R.string.buttons))
+ add(ButtonInputSetting(playerIndex, NativeButton.A, R.string.button_a))
+ add(ButtonInputSetting(playerIndex, NativeButton.B, R.string.button_b))
+ add(ButtonInputSetting(playerIndex, NativeButton.X, R.string.button_x))
+ add(ButtonInputSetting(playerIndex, NativeButton.Y, R.string.button_y))
+ add(ButtonInputSetting(playerIndex, NativeButton.Plus, R.string.start_pause))
+ }
+
+ else -> {
+ // No-op
+ }
+ }
+
+ when (styleIndex) {
+ NpadStyleIndex.Fullkey,
+ NpadStyleIndex.Handheld,
+ NpadStyleIndex.JoyconDual,
+ NpadStyleIndex.JoyconLeft -> {
+ add(HeaderSetting(R.string.dpad))
+ add(ButtonInputSetting(playerIndex, NativeButton.DUp, R.string.up))
+ add(ButtonInputSetting(playerIndex, NativeButton.DDown, R.string.down))
+ add(ButtonInputSetting(playerIndex, NativeButton.DLeft, R.string.left))
+ add(ButtonInputSetting(playerIndex, NativeButton.DRight, R.string.right))
+ }
+
+ else -> {
+ // No-op
+ }
+ }
+
+ // Left stick
+ when (styleIndex) {
+ NpadStyleIndex.Fullkey,
+ NpadStyleIndex.Handheld,
+ NpadStyleIndex.JoyconDual,
+ NpadStyleIndex.JoyconLeft -> {
+ add(HeaderSetting(R.string.left_stick))
+ addAll(getStickDirections(playerIndex, NativeAnalog.LStick))
+ add(ButtonInputSetting(playerIndex, NativeButton.LStick, R.string.pressed))
+ addAll(getExtraStickSettings(playerIndex, NativeAnalog.LStick))
+ }
+
+ NpadStyleIndex.GameCube -> {
+ add(HeaderSetting(R.string.control_stick))
+ addAll(getStickDirections(playerIndex, NativeAnalog.LStick))
+ addAll(getExtraStickSettings(playerIndex, NativeAnalog.LStick))
+ }
+
+ else -> {
+ // No-op
+ }
+ }
+
+ // Right stick
+ when (styleIndex) {
+ NpadStyleIndex.Fullkey,
+ NpadStyleIndex.Handheld,
+ NpadStyleIndex.JoyconDual,
+ NpadStyleIndex.JoyconRight -> {
+ add(HeaderSetting(R.string.right_stick))
+ addAll(getStickDirections(playerIndex, NativeAnalog.RStick))
+ add(ButtonInputSetting(playerIndex, NativeButton.RStick, R.string.pressed))
+ addAll(getExtraStickSettings(playerIndex, NativeAnalog.RStick))
+ }
+
+ NpadStyleIndex.GameCube -> {
+ add(HeaderSetting(R.string.c_stick))
+ addAll(getStickDirections(playerIndex, NativeAnalog.RStick))
+ addAll(getExtraStickSettings(playerIndex, NativeAnalog.RStick))
+ }
+
+ else -> {
+ // No-op
+ }
+ }
+
+ // L/R, ZL/ZR, and SL/SR
+ when (styleIndex) {
+ NpadStyleIndex.Fullkey,
+ NpadStyleIndex.Handheld -> {
+ add(HeaderSetting(R.string.triggers))
+ add(ButtonInputSetting(playerIndex, NativeButton.L, R.string.button_l))
+ add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_r))
+ add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_zl))
+ add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_zr))
+ }
+
+ NpadStyleIndex.JoyconDual -> {
+ add(HeaderSetting(R.string.triggers))
+ add(ButtonInputSetting(playerIndex, NativeButton.L, R.string.button_l))
+ add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_r))
+ add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_zl))
+ add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_zr))
+ add(
+ ButtonInputSetting(
+ playerIndex,
+ NativeButton.SLLeft,
+ R.string.button_sl_left
+ )
+ )
+ add(
+ ButtonInputSetting(
+ playerIndex,
+ NativeButton.SRLeft,
+ R.string.button_sr_left
+ )
+ )
+ add(
+ ButtonInputSetting(
+ playerIndex,
+ NativeButton.SLRight,
+ R.string.button_sl_right
+ )
+ )
+ add(
+ ButtonInputSetting(
+ playerIndex,
+ NativeButton.SRRight,
+ R.string.button_sr_right
+ )
+ )
+ }
+
+ NpadStyleIndex.JoyconLeft -> {
+ add(HeaderSetting(R.string.triggers))
+ add(ButtonInputSetting(playerIndex, NativeButton.L, R.string.button_l))
+ add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_zl))
+ add(
+ ButtonInputSetting(
+ playerIndex,
+ NativeButton.SLLeft,
+ R.string.button_sl_left
+ )
+ )
+ add(
+ ButtonInputSetting(
+ playerIndex,
+ NativeButton.SRLeft,
+ R.string.button_sr_left
+ )
+ )
+ }
+
+ NpadStyleIndex.JoyconRight -> {
+ add(HeaderSetting(R.string.triggers))
+ add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_r))
+ add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_zr))
+ add(
+ ButtonInputSetting(
+ playerIndex,
+ NativeButton.SLRight,
+ R.string.button_sl_right
+ )
+ )
+ add(
+ ButtonInputSetting(
+ playerIndex,
+ NativeButton.SRRight,
+ R.string.button_sr_right
+ )
+ )
+ }
+
+ NpadStyleIndex.GameCube -> {
+ add(HeaderSetting(R.string.triggers))
+ add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_z))
+ add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_l))
+ add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_r))
+ }
+
+ else -> {
+ // No-op
+ }
+ }
+
+ add(HeaderSetting(R.string.vibration))
+ val vibrationEnabledSetting = object : AbstractBooleanSetting {
+ override val key = "vibration"
+
+ override fun getBoolean(needsGlobal: Boolean): Boolean =
+ NativeConfig.getInputSettings(true)[playerIndex].vibrationEnabled
+
+ override fun setBoolean(value: Boolean) {
+ val settings = NativeConfig.getInputSettings(true)
+ settings[playerIndex].vibrationEnabled = value
+ NativeConfig.setInputSettings(settings, true)
+ }
+
+ override val defaultValue = true
+
+ override fun getValueAsString(needsGlobal: Boolean): String =
+ getBoolean(needsGlobal).toString()
+
+ override fun reset() = setBoolean(defaultValue)
+ }
+ add(SwitchSetting(vibrationEnabledSetting, R.string.vibration))
+
+ val useSystemVibratorSetting = object : AbstractBooleanSetting {
+ override val key = ""
+
+ override fun getBoolean(needsGlobal: Boolean): Boolean =
+ NativeConfig.getInputSettings(true)[playerIndex].useSystemVibrator
+
+ override fun setBoolean(value: Boolean) {
+ val settings = NativeConfig.getInputSettings(true)
+ settings[playerIndex].useSystemVibrator = value
+ NativeConfig.setInputSettings(settings, true)
+ }
+
+ override val defaultValue = playerIndex == 0
+
+ override fun getValueAsString(needsGlobal: Boolean): String =
+ getBoolean(needsGlobal).toString()
+
+ override fun reset() = setBoolean(defaultValue)
+
+ override val pairedSettingKey: String = "vibration"
+ }
+ addAbstract(SwitchSetting(useSystemVibratorSetting, R.string.use_system_vibrator))
+
+ val vibrationStrengthSetting = object : AbstractIntSetting {
+ override val key = ""
+
+ override fun getInt(needsGlobal: Boolean): Int =
+ NativeConfig.getInputSettings(true)[playerIndex].vibrationStrength
+
+ override fun setInt(value: Int) {
+ val settings = NativeConfig.getInputSettings(true)
+ settings[playerIndex].vibrationStrength = value
+ NativeConfig.setInputSettings(settings, true)
+ }
+
+ override val defaultValue = 100
+
+ override fun getValueAsString(needsGlobal: Boolean): String =
+ getInt(needsGlobal).toString()
+
+ override fun reset() = setInt(defaultValue)
+
+ override val pairedSettingKey: String = "vibration"
+ }
+ addAbstract(
+ SliderSetting(vibrationStrengthSetting, R.string.vibration_strength, units = "%")
+ )
+ }
+ }
+
+ // Convenience function for creating AbstractIntSettings for modifier range/stick range/stick deadzones
+ private fun getStickIntSettingFromParam(
+ playerIndex: Int,
+ paramName: String,
+ stick: NativeAnalog,
+ defaultValue: Int
+ ): AbstractIntSetting =
+ object : AbstractIntSetting {
+ val params get() = NativeInput.getStickParam(playerIndex, stick)
+
+ override val key = ""
+
+ override fun getInt(needsGlobal: Boolean): Int =
+ (params.get(paramName, 0.15f) * 100).toInt()
+
+ override fun setInt(value: Int) {
+ val tempParams = params
+ tempParams.set(paramName, value.toFloat() / 100)
+ NativeInput.setStickParam(playerIndex, stick, tempParams)
+ }
+
+ override val defaultValue = defaultValue
+
+ override fun getValueAsString(needsGlobal: Boolean): String =
+ getInt(needsGlobal).toString()
+
+ override fun reset() = setInt(defaultValue)
+ }
+
+ private fun getExtraStickSettings(
+ playerIndex: Int,
+ nativeAnalog: NativeAnalog
+ ): List<SettingsItem> {
+ val stickIsController =
+ NativeInput.isController(NativeInput.getStickParam(playerIndex, nativeAnalog))
+ val modifierRangeSetting =
+ getStickIntSettingFromParam(playerIndex, "modifier_scale", nativeAnalog, 50)
+ val stickRangeSetting =
+ getStickIntSettingFromParam(playerIndex, "range", nativeAnalog, 95)
+ val stickDeadzoneSetting =
+ getStickIntSettingFromParam(playerIndex, "deadzone", nativeAnalog, 15)
+
+ val out = mutableListOf<SettingsItem>().apply {
+ if (stickIsController) {
+ add(SliderSetting(stickRangeSetting, titleId = R.string.range, min = 25, max = 150))
+ add(SliderSetting(stickDeadzoneSetting, R.string.deadzone))
+ } else {
+ add(ModifierInputSetting(playerIndex, NativeAnalog.LStick, R.string.modifier))
+ add(SliderSetting(modifierRangeSetting, R.string.modifier_range))
+ }
+ }
+ return out
+ }
+
+ private fun getStickDirections(player: Int, stick: NativeAnalog): List<AnalogInputSetting> =
+ listOf(
+ AnalogInputSetting(
+ player,
+ stick,
+ AnalogDirection.Up,
+ R.string.up
+ ),
+ AnalogInputSetting(
+ player,
+ stick,
+ AnalogDirection.Down,
+ R.string.down
+ ),
+ AnalogInputSetting(
+ player,
+ stick,
+ AnalogDirection.Left,
+ R.string.left
+ ),
+ AnalogInputSetting(
+ player,
+ stick,
+ AnalogDirection.Right,
+ R.string.right
+ )
+ )
+
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
sl.apply {
val theme: AbstractIntSetting = object : AbstractIntSetting {
@@ -186,20 +877,18 @@ class SettingsFragmentPresenter(
add(
SingleChoiceSetting(
theme,
- R.string.change_app_theme,
- 0,
- R.array.themeEntriesA12,
- R.array.themeValuesA12
+ titleId = R.string.change_app_theme,
+ choicesId = R.array.themeEntriesA12,
+ valuesId = R.array.themeValuesA12
)
)
} else {
add(
SingleChoiceSetting(
theme,
- R.string.change_app_theme,
- 0,
- R.array.themeEntries,
- R.array.themeValues
+ titleId = R.string.change_app_theme,
+ choicesId = R.array.themeEntries,
+ valuesId = R.array.themeValues
)
)
}
@@ -228,10 +917,9 @@ class SettingsFragmentPresenter(
add(
SingleChoiceSetting(
themeMode,
- R.string.change_theme_mode,
- 0,
- R.array.themeModeEntries,
- R.array.themeModeValues
+ titleId = R.string.change_theme_mode,
+ choicesId = R.array.themeModeEntries,
+ valuesId = R.array.themeModeValues
)
)
@@ -262,8 +950,8 @@ class SettingsFragmentPresenter(
add(
SwitchSetting(
blackBackgrounds,
- R.string.use_black_backgrounds,
- R.string.use_black_backgrounds_description
+ titleId = R.string.use_black_backgrounds,
+ descriptionId = R.string.use_black_backgrounds_description
)
)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSearchFragment.kt
index a135b80b4..ed60cf34f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSearchFragment.kt
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-package org.yuzu.yuzu_emu.fragments
+package org.yuzu.yuzu_emu.features.settings.ui
import android.content.Context
import android.os.Bundle
@@ -15,21 +15,17 @@ import androidx.core.view.updatePadding
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.divider.MaterialDividerItemDecoration
import com.google.android.material.transition.MaterialSharedAxis
import info.debatty.java.stringsimilarity.Cosine
-import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
-import org.yuzu.yuzu_emu.model.SettingsViewModel
import org.yuzu.yuzu_emu.utils.NativeConfig
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
+import org.yuzu.yuzu_emu.utils.collect
class SettingsSearchFragment : Fragment() {
private var _binding: FragmentSettingsSearchBinding? = null
@@ -85,14 +81,10 @@ class SettingsSearchFragment : Fragment() {
search()
binding.settingsList.smoothScrollToPosition(0)
}
- viewLifecycleOwner.lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- settingsViewModel.shouldReloadSettingsList.collect {
- if (it) {
- settingsViewModel.setShouldReloadSettingsList(false)
- search()
- }
- }
+ settingsViewModel.shouldReloadSettingsList.collect(viewLifecycleOwner) {
+ if (it) {
+ settingsViewModel.setShouldReloadSettingsList(false)
+ search()
}
}
@@ -108,10 +100,9 @@ class SettingsSearchFragment : Fragment() {
private fun search() {
val searchTerm = binding.searchText.text.toString().lowercase()
- binding.clearButton.visibility =
- if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE
+ binding.clearButton.setVisible(visible = searchTerm.isNotEmpty(), gone = false)
if (searchTerm.isEmpty()) {
- binding.noResultsView.visibility = View.VISIBLE
+ binding.noResultsView.setVisible(visible = false, gone = false)
settingsAdapter?.submitList(emptyList())
return
}
@@ -119,7 +110,7 @@ class SettingsSearchFragment : Fragment() {
val baseList = SettingsItem.settingsItems
val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1)
val sortedList: List<SettingsItem> = baseList.mapNotNull { item ->
- val title = getString(item.value.nameId).lowercase()
+ val title = item.value.title.lowercase()
val similarity = similarityAlgorithm.similarity(searchTerm, title)
if (similarity > 0.08) {
Pair(similarity, item)
@@ -138,8 +129,7 @@ class SettingsSearchFragment : Fragment() {
optionalSetting
}
settingsAdapter?.submitList(sortedList)
- binding.noResultsView.visibility =
- if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE
+ binding.noResultsView.setVisible(visible = sortedList.isEmpty(), gone = false)
}
private fun focusSearch() {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsViewModel.kt
index 5cb6a5d57..fbdca04e9 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsViewModel.kt
@@ -1,20 +1,26 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-package org.yuzu.yuzu_emu.model
+package org.yuzu.yuzu_emu.features.settings.ui
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.model.Game
+import org.yuzu.yuzu_emu.utils.InputHandler
+import org.yuzu.yuzu_emu.utils.ParamPackage
class SettingsViewModel : ViewModel() {
var game: Game? = null
var clickedItem: SettingsItem? = null
+ var currentDevice = 0
+
val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
private val _shouldRecreate = MutableStateFlow(false)
@@ -36,6 +42,18 @@ class SettingsViewModel : ViewModel() {
val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
private val _adapterItemChanged = MutableStateFlow(-1)
+ private val _datasetChanged = MutableStateFlow(false)
+ val datasetChanged = _datasetChanged.asStateFlow()
+
+ private val _reloadListAndNotifyDataset = MutableStateFlow(false)
+ val reloadListAndNotifyDataset = _reloadListAndNotifyDataset.asStateFlow()
+
+ private val _shouldShowDeleteProfileDialog = MutableStateFlow("")
+ val shouldShowDeleteProfileDialog = _shouldShowDeleteProfileDialog.asStateFlow()
+
+ private val _shouldShowResetInputDialog = MutableStateFlow(false)
+ val shouldShowResetInputDialog = _shouldShowResetInputDialog.asStateFlow()
+
fun setShouldRecreate(value: Boolean) {
_shouldRecreate.value = value
}
@@ -68,4 +86,27 @@ class SettingsViewModel : ViewModel() {
fun setAdapterItemChanged(value: Int) {
_adapterItemChanged.value = value
}
+
+ fun setDatasetChanged(value: Boolean) {
+ _datasetChanged.value = value
+ }
+
+ fun setReloadListAndNotifyDataset(value: Boolean) {
+ _reloadListAndNotifyDataset.value = value
+ }
+
+ fun setShouldShowDeleteProfileDialog(profile: String) {
+ _shouldShowDeleteProfileDialog.value = profile
+ }
+
+ fun setShouldShowResetInputDialog(value: Boolean) {
+ _shouldShowResetInputDialog.value = value
+ }
+
+ fun getCurrentDeviceParams(defaultParams: ParamPackage): ParamPackage =
+ try {
+ InputHandler.registeredControllers[currentDevice]
+ } catch (e: IndexOutOfBoundsException) {
+ defaultParams
+ }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
index 5ad0899dd..367db7fd2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
@@ -14,6 +14,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.NativeConfig
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -21,28 +22,19 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
override fun bind(item: SettingsItem) {
setting = item as DateTimeSetting
- binding.textSettingName.setText(item.nameId)
- if (item.descriptionId != 0) {
- binding.textSettingDescription.setText(item.descriptionId)
- binding.textSettingDescription.visibility = View.VISIBLE
- } else {
- binding.textSettingDescription.visibility = View.GONE
- }
-
- binding.textSettingValue.visibility = View.VISIBLE
+ binding.textSettingName.text = item.title
+ binding.textSettingDescription.setVisible(item.description.isNotEmpty())
+ binding.textSettingDescription.text = item.description
+ binding.textSettingValue.setVisible(true)
val epochTime = setting.getValue()
val instant = Instant.ofEpochMilli(epochTime * 1000)
val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
binding.textSettingValue.text = dateFormatter.format(zonedTime)
- binding.buttonClear.visibility = if (setting.setting.global ||
- !NativeConfig.isPerGameConfigLoaded()
- ) {
- View.GONE
- } else {
- View.VISIBLE
- }
+ binding.buttonClear.setVisible(
+ !setting.setting.global || NativeConfig.isPerGameConfigLoaded()
+ )
binding.buttonClear.setOnClickListener {
adapter.onClearClick(setting, bindingAdapterPosition)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/HeaderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/HeaderViewHolder.kt
index f5bcf705c..0815c36e2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/HeaderViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/HeaderViewHolder.kt
@@ -16,7 +16,7 @@ class HeaderViewHolder(val binding: ListItemSettingsHeaderBinding, adapter: Sett
}
override fun bind(item: SettingsItem) {
- binding.textHeaderName.setText(item.nameId)
+ binding.textHeaderName.text = item.title
}
override fun onClick(clicked: View) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/InputProfileViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/InputProfileViewHolder.kt
new file mode 100644
index 000000000..86af3a226
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/InputProfileViewHolder.kt
@@ -0,0 +1,34 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.ui.viewholder
+
+import android.view.View
+import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
+import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
+
+class InputProfileViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
+ SettingViewHolder(binding.root, adapter) {
+ private lateinit var setting: InputProfileSetting
+
+ override fun bind(item: SettingsItem) {
+ setting = item as InputProfileSetting
+ binding.textSettingName.text = setting.title
+ binding.textSettingValue.text =
+ setting.getCurrentProfile().ifEmpty { binding.root.context.getString(R.string.not_set) }
+
+ binding.textSettingDescription.setVisible(false)
+ binding.buttonClear.setVisible(false)
+ binding.icon.setVisible(false)
+ binding.buttonClear.setVisible(false)
+ }
+
+ override fun onClick(clicked: View) =
+ adapter.onInputProfileClick(setting, bindingAdapterPosition)
+
+ override fun onLongClick(clicked: View): Boolean = false
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/InputViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/InputViewHolder.kt
new file mode 100644
index 000000000..9d9047804
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/InputViewHolder.kt
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.ui.viewholder
+
+import android.view.View
+import org.yuzu.yuzu_emu.databinding.ListItemSettingInputBinding
+import org.yuzu.yuzu_emu.features.input.NativeInput
+import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.ButtonInputSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.InputSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.ModifierInputSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
+
+class InputViewHolder(val binding: ListItemSettingInputBinding, adapter: SettingsAdapter) :
+ SettingViewHolder(binding.root, adapter) {
+ private lateinit var setting: InputSetting
+
+ override fun bind(item: SettingsItem) {
+ setting = item as InputSetting
+ binding.textSettingName.text = setting.title
+ binding.textSettingValue.text = setting.getSelectedValue()
+
+ when (item) {
+ is AnalogInputSetting -> {
+ val param = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog)
+ binding.buttonOptions.setVisible(
+ param.get("engine", "") == "analog_from_button" ||
+ param.has("axis_x") || param.has("axis_y")
+ )
+ }
+
+ is ButtonInputSetting -> {
+ val param = NativeInput.getButtonParam(item.playerIndex, item.nativeButton)
+ binding.buttonOptions.setVisible(
+ param.has("code") || param.has("button") || param.has("hat") ||
+ param.has("axis")
+ )
+ }
+
+ is ModifierInputSetting -> {
+ val params = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog)
+ binding.buttonOptions.setVisible(params.has("modifier"))
+ }
+ }
+
+ binding.buttonOptions.setOnClickListener(null)
+ binding.buttonOptions.setOnClickListener {
+ adapter.onInputOptionsClick(binding.buttonOptions, setting, bindingAdapterPosition)
+ }
+ }
+
+ override fun onClick(clicked: View) =
+ adapter.onInputClick(setting, bindingAdapterPosition)
+
+ override fun onLongClick(clicked: View): Boolean =
+ adapter.onLongClick(setting, bindingAdapterPosition)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
index 507184238..fc2ffb515 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
@@ -5,11 +5,11 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View
import androidx.core.content.res.ResourcesCompat
-import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.RunnableSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -17,34 +17,28 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
override fun bind(item: SettingsItem) {
setting = item as RunnableSetting
- if (item.iconId != 0) {
- binding.icon.visibility = View.VISIBLE
+ binding.icon.setVisible(setting.iconId != 0)
+ if (setting.iconId != 0) {
binding.icon.setImageDrawable(
ResourcesCompat.getDrawable(
binding.icon.resources,
- item.iconId,
+ setting.iconId,
binding.icon.context.theme
)
)
- } else {
- binding.icon.visibility = View.GONE
}
- binding.textSettingName.setText(item.nameId)
- if (item.descriptionId != 0) {
- binding.textSettingDescription.setText(item.descriptionId)
- binding.textSettingDescription.visibility = View.VISIBLE
- } else {
- binding.textSettingDescription.visibility = View.GONE
- }
- binding.textSettingValue.visibility = View.GONE
- binding.buttonClear.visibility = View.GONE
+ binding.textSettingName.text = setting.title
+ binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
+ binding.textSettingDescription.text = item.description
+ binding.textSettingValue.setVisible(false)
+ binding.buttonClear.setVisible(false)
setStyle(setting.isEditable, binding)
}
override fun onClick(clicked: View) {
- if (!setting.isRuntimeRunnable && !NativeLibrary.isRunning()) {
+ if (setting.isRunnable) {
setting.runnable.invoke()
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
index 02dab3785..e2fe0b072 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
@@ -5,11 +5,13 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
+import org.yuzu.yuzu_emu.features.settings.model.view.IntSingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.NativeConfig
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -17,40 +19,38 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
override fun bind(item: SettingsItem) {
setting = item
- binding.textSettingName.setText(item.nameId)
- if (item.descriptionId != 0) {
- binding.textSettingDescription.setText(item.descriptionId)
- binding.textSettingDescription.visibility = View.VISIBLE
- } else {
- binding.textSettingDescription.visibility = View.GONE
- }
+ binding.textSettingName.text = setting.title
+ binding.textSettingDescription.setVisible(item.description.isNotEmpty())
+ binding.textSettingDescription.text = item.description
- binding.textSettingValue.visibility = View.VISIBLE
- if (item is SingleChoiceSetting) {
- val resMgr = binding.textSettingValue.context.resources
- val values = resMgr.getIntArray(item.valuesId)
- for (i in values.indices) {
- if (values[i] == item.getSelectedValue()) {
- binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i]
- break
+ binding.textSettingValue.setVisible(true)
+ when (item) {
+ is SingleChoiceSetting -> {
+ val resMgr = binding.textSettingValue.context.resources
+ val values = resMgr.getIntArray(item.valuesId)
+ for (i in values.indices) {
+ if (values[i] == item.getSelectedValue()) {
+ binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i]
+ break
+ }
}
}
- } else if (item is StringSingleChoiceSetting) {
- for (i in item.values.indices) {
- if (item.values[i] == item.getSelectedValue()) {
- binding.textSettingValue.text = item.choices[i]
- break
- }
+
+ is StringSingleChoiceSetting -> {
+ binding.textSettingValue.text = item.getSelectedValue()
}
- }
- binding.buttonClear.visibility = if (setting.setting.global ||
- !NativeConfig.isPerGameConfigLoaded()
- ) {
- View.GONE
- } else {
- View.VISIBLE
+ is IntSingleChoiceSetting -> {
+ binding.textSettingValue.text = item.getChoiceAt(item.getSelectedValue())
+ }
+ }
+ if (binding.textSettingValue.text.isEmpty()) {
+ binding.textSettingValue.setVisible(false)
}
+
+ binding.buttonClear.setVisible(
+ !setting.setting.global || NativeConfig.isPerGameConfigLoaded()
+ )
binding.buttonClear.setOnClickListener {
adapter.onClearClick(setting, bindingAdapterPosition)
}
@@ -63,16 +63,25 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
return
}
- if (setting is SingleChoiceSetting) {
- adapter.onSingleChoiceClick(
- (setting as SingleChoiceSetting),
- bindingAdapterPosition
- )
- } else if (setting is StringSingleChoiceSetting) {
- adapter.onStringSingleChoiceClick(
- (setting as StringSingleChoiceSetting),
+ when (setting) {
+ is SingleChoiceSetting -> adapter.onSingleChoiceClick(
+ setting as SingleChoiceSetting,
bindingAdapterPosition
)
+
+ is StringSingleChoiceSetting -> {
+ adapter.onStringSingleChoiceClick(
+ setting as StringSingleChoiceSetting,
+ bindingAdapterPosition
+ )
+ }
+
+ is IntSingleChoiceSetting -> {
+ adapter.onIntSingleChoiceClick(
+ setting as IntSingleChoiceSetting,
+ bindingAdapterPosition
+ )
+ }
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
index 596c18012..a37b59b44 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
@@ -10,6 +10,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.NativeConfig
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -17,27 +18,19 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
override fun bind(item: SettingsItem) {
setting = item as SliderSetting
- binding.textSettingName.setText(item.nameId)
- if (item.descriptionId != 0) {
- binding.textSettingDescription.setText(item.descriptionId)
- binding.textSettingDescription.visibility = View.VISIBLE
- } else {
- binding.textSettingDescription.visibility = View.GONE
- }
- binding.textSettingValue.visibility = View.VISIBLE
+ binding.textSettingName.text = setting.title
+ binding.textSettingDescription.setVisible(item.description.isNotEmpty())
+ binding.textSettingDescription.text = setting.description
+ binding.textSettingValue.setVisible(true)
binding.textSettingValue.text = String.format(
binding.textSettingValue.context.getString(R.string.value_with_units),
setting.getSelectedValue(),
setting.units
)
- binding.buttonClear.visibility = if (setting.setting.global ||
- !NativeConfig.isPerGameConfigLoaded()
- ) {
- View.GONE
- } else {
- View.VISIBLE
- }
+ binding.buttonClear.setVisible(
+ !setting.setting.global || NativeConfig.isPerGameConfigLoaded()
+ )
binding.buttonClear.setOnClickListener {
adapter.onClearClick(setting, bindingAdapterPosition)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
index 20d35a17d..f7a9c08c3 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
@@ -9,39 +9,34 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SubmenuSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
- private lateinit var item: SubmenuSetting
+ private lateinit var setting: SubmenuSetting
override fun bind(item: SettingsItem) {
- this.item = item as SubmenuSetting
- if (item.iconId != 0) {
- binding.icon.visibility = View.VISIBLE
+ setting = item as SubmenuSetting
+ binding.icon.setVisible(setting.iconId != 0)
+ if (setting.iconId != 0) {
binding.icon.setImageDrawable(
ResourcesCompat.getDrawable(
binding.icon.resources,
- item.iconId,
+ setting.iconId,
binding.icon.context.theme
)
)
- } else {
- binding.icon.visibility = View.GONE
}
- binding.textSettingName.setText(item.nameId)
- if (item.descriptionId != 0) {
- binding.textSettingDescription.setText(item.descriptionId)
- binding.textSettingDescription.visibility = View.VISIBLE
- } else {
- binding.textSettingDescription.visibility = View.GONE
- }
- binding.textSettingValue.visibility = View.GONE
- binding.buttonClear.visibility = View.GONE
+ binding.textSettingName.text = setting.title
+ binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
+ binding.textSettingDescription.text = setting.description
+ binding.textSettingValue.setVisible(false)
+ binding.buttonClear.setVisible(false)
}
override fun onClick(clicked: View) {
- adapter.onSubmenuClick(item)
+ adapter.onSubmenuClick(setting)
}
override fun onLongClick(clicked: View): Boolean {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
index d26bf9374..53f7b301f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
@@ -10,6 +10,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.NativeConfig
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -18,28 +19,19 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
override fun bind(item: SettingsItem) {
setting = item as SwitchSetting
- binding.textSettingName.setText(item.nameId)
- if (item.descriptionId != 0) {
- binding.textSettingDescription.setText(item.descriptionId)
- binding.textSettingDescription.visibility = View.VISIBLE
- } else {
- binding.textSettingDescription.text = ""
- binding.textSettingDescription.visibility = View.GONE
- }
+ binding.textSettingName.text = setting.title
+ binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
+ binding.textSettingDescription.text = setting.description
binding.switchWidget.setOnCheckedChangeListener(null)
binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal)
binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
- adapter.onBooleanClick(item, binding.switchWidget.isChecked, bindingAdapterPosition)
+ adapter.onBooleanClick(setting, binding.switchWidget.isChecked, bindingAdapterPosition)
}
- binding.buttonClear.visibility = if (setting.setting.global ||
- !NativeConfig.isPerGameConfigLoaded()
- ) {
- View.GONE
- } else {
- View.VISIBLE
- }
+ binding.buttonClear.setVisible(
+ !setting.setting.global || NativeConfig.isPerGameConfigLoaded()
+ )
binding.buttonClear.setOnClickListener {
adapter.onClearClick(setting, bindingAdapterPosition)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt
index 872553ac4..110aa2960 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt
@@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.fragments
-import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
@@ -16,9 +15,6 @@ import androidx.core.view.updatePadding
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
@@ -32,6 +28,7 @@ import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.AddonUtil
import org.yuzu.yuzu_emu.utils.FileUtil.copyFilesTo
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
+import org.yuzu.yuzu_emu.utils.collect
import java.io.File
class AddonsFragment : Fragment() {
@@ -60,8 +57,6 @@ class AddonsFragment : Fragment() {
return binding.root
}
- // This is using the correct scope, lint is just acting up
- @SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = false)
@@ -78,57 +73,41 @@ class AddonsFragment : Fragment() {
adapter = AddonAdapter(addonViewModel)
}
- viewLifecycleOwner.lifecycleScope.apply {
- launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- addonViewModel.addonList.collect {
- (binding.listAddons.adapter as AddonAdapter).submitList(it)
- }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- addonViewModel.showModInstallPicker.collect {
- if (it) {
- installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
- addonViewModel.showModInstallPicker(false)
- }
- }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- addonViewModel.showModNoticeDialog.collect {
- if (it) {
- MessageDialogFragment.newInstance(
- requireActivity(),
- titleId = R.string.addon_notice,
- descriptionId = R.string.addon_notice_description,
- dismissible = false,
- positiveAction = { addonViewModel.showModInstallPicker(true) },
- negativeAction = {},
- negativeButtonTitleId = R.string.close
- ).show(parentFragmentManager, MessageDialogFragment.TAG)
- addonViewModel.showModNoticeDialog(false)
- }
- }
- }
+ addonViewModel.addonList.collect(viewLifecycleOwner) {
+ (binding.listAddons.adapter as AddonAdapter).submitList(it)
+ }
+ addonViewModel.showModInstallPicker.collect(
+ viewLifecycleOwner,
+ resetState = { addonViewModel.showModInstallPicker(false) }
+ ) { if (it) installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) }
+ addonViewModel.showModNoticeDialog.collect(
+ viewLifecycleOwner,
+ resetState = { addonViewModel.showModNoticeDialog(false) }
+ ) {
+ if (it) {
+ MessageDialogFragment.newInstance(
+ requireActivity(),
+ titleId = R.string.addon_notice,
+ descriptionId = R.string.addon_notice_description,
+ dismissible = false,
+ positiveAction = { addonViewModel.showModInstallPicker(true) },
+ negativeAction = {},
+ negativeButtonTitleId = R.string.close
+ ).show(parentFragmentManager, MessageDialogFragment.TAG)
}
- launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- addonViewModel.addonToDelete.collect {
- if (it != null) {
- MessageDialogFragment.newInstance(
- requireActivity(),
- titleId = R.string.confirm_uninstall,
- descriptionId = R.string.confirm_uninstall_description,
- positiveAction = { addonViewModel.onDeleteAddon(it) },
- negativeAction = {}
- ).show(parentFragmentManager, MessageDialogFragment.TAG)
- addonViewModel.setAddonToDelete(null)
- }
- }
- }
+ }
+ addonViewModel.addonToDelete.collect(
+ viewLifecycleOwner,
+ resetState = { addonViewModel.setAddonToDelete(null) }
+ ) {
+ if (it != null) {
+ MessageDialogFragment.newInstance(
+ requireActivity(),
+ titleId = R.string.confirm_uninstall,
+ descriptionId = R.string.confirm_uninstall_description,
+ positiveAction = { addonViewModel.onDeleteAddon(it) },
+ negativeAction = {}
+ ).show(parentFragmentManager, MessageDialogFragment.TAG)
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CoreErrorDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CoreErrorDialogFragment.kt
new file mode 100644
index 000000000..299f8da71
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CoreErrorDialogFragment.kt
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.R
+
+class CoreErrorDialogFragment : DialogFragment() {
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
+ MaterialAlertDialogBuilder(requireActivity())
+ .setTitle(requireArguments().getString(TITLE))
+ .setMessage(requireArguments().getString(MESSAGE))
+ .setPositiveButton(R.string.continue_button, null)
+ .setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int ->
+ NativeLibrary.coreErrorAlertResult = false
+ synchronized(NativeLibrary.coreErrorAlertLock) {
+ NativeLibrary.coreErrorAlertLock.notify()
+ }
+ }
+ .create()
+
+ override fun onDismiss(dialog: DialogInterface) {
+ super.onDismiss(dialog)
+ NativeLibrary.coreErrorAlertResult = true
+ synchronized(NativeLibrary.coreErrorAlertLock) { NativeLibrary.coreErrorAlertLock.notify() }
+ }
+
+ companion object {
+ const val TITLE = "Title"
+ const val MESSAGE = "Message"
+
+ fun newInstance(title: String, message: String): CoreErrorDialogFragment {
+ val frag = CoreErrorDialogFragment()
+ val args = Bundle()
+ args.putString(TITLE, title)
+ args.putString(MESSAGE, message)
+ frag.arguments = args
+ return frag
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
index 41cff46c1..8b23a1021 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
@@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.fragments
-import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -14,9 +13,6 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
@@ -35,6 +31,7 @@ import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
+import org.yuzu.yuzu_emu.utils.collect
import java.io.File
import java.io.IOException
@@ -63,8 +60,6 @@ class DriverManagerFragment : Fragment() {
return binding.root
}
- // This is using the correct scope, lint is just acting up
- @SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = true)
@@ -89,15 +84,8 @@ class DriverManagerFragment : Fragment() {
}
}
- viewLifecycleOwner.lifecycleScope.apply {
- launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- driverViewModel.showClearButton.collect {
- binding.toolbarDrivers.menu
- .findItem(R.id.menu_driver_use_global).isVisible = it
- }
- }
- }
+ driverViewModel.showClearButton.collect(viewLifecycleOwner) {
+ binding.toolbarDrivers.menu.findItem(R.id.menu_driver_use_global).isVisible = it
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt
index 6a47b29f0..bad56e434 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt
@@ -10,14 +10,11 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.DriverViewModel
+import org.yuzu.yuzu_emu.utils.collect
class DriversLoadingDialogFragment : DialogFragment() {
private val driverViewModel: DriverViewModel by activityViewModels()
@@ -44,13 +41,7 @@ class DriversLoadingDialogFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- viewLifecycleOwner.lifecycleScope.apply {
- launch {
- repeatOnLifecycle(Lifecycle.State.RESUMED) {
- driverViewModel.isInteractionAllowed.collect { if (it) dismiss() }
- }
- }
- }
+ driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) { if (it) dismiss() }
}
companion object {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 6b25cc525..bcc880e17 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -32,9 +32,6 @@ import androidx.drawerlayout.widget.DrawerLayout
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.window.layout.FoldingFeature
@@ -42,9 +39,6 @@ import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
@@ -63,6 +57,7 @@ import org.yuzu.yuzu_emu.model.EmulationViewModel
import org.yuzu.yuzu_emu.overlay.model.OverlayControl
import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
import org.yuzu.yuzu_emu.utils.*
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import java.lang.NullPointerException
class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@@ -90,14 +85,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
if (context is EmulationActivity) {
emulationActivity = context
NativeLibrary.setEmulationActivity(context)
-
- lifecycleScope.launch(Dispatchers.Main) {
- lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
- WindowInfoTracker.getOrCreate(context)
- .windowLayoutInfo(context)
- .collect { updateFoldableLayout(context, it) }
- }
- }
} else {
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
}
@@ -168,8 +155,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
return binding.root
}
- // This is using the correct scope, lint is just acting up
- @SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (requireActivity().isFinishing) {
@@ -277,6 +262,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
true
}
+ R.id.menu_controls -> {
+ val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+ null,
+ Settings.MenuTag.SECTION_INPUT
+ )
+ binding.root.findNavController().navigate(action)
+ true
+ }
+
R.id.menu_overlay_controls -> {
showOverlayOptions()
true
@@ -341,129 +335,86 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.loadingTitle.isSelected = true
binding.loadingText.isSelected = true
- viewLifecycleOwner.lifecycleScope.apply {
- launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- WindowInfoTracker.getOrCreate(requireContext())
- .windowLayoutInfo(requireActivity())
- .collect {
- updateFoldableLayout(requireActivity() as EmulationActivity, it)
- }
- }
+ WindowInfoTracker.getOrCreate(requireContext())
+ .windowLayoutInfo(requireActivity()).collect(viewLifecycleOwner) {
+ updateFoldableLayout(requireActivity() as EmulationActivity, it)
}
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- emulationViewModel.shaderProgress.collectLatest {
- if (it > 0 && it != emulationViewModel.totalShaders.value) {
- binding.loadingProgressIndicator.isIndeterminate = false
-
- if (it < binding.loadingProgressIndicator.max) {
- binding.loadingProgressIndicator.progress = it
- }
- }
+ emulationViewModel.shaderProgress.collect(viewLifecycleOwner) {
+ if (it > 0 && it != emulationViewModel.totalShaders.value) {
+ binding.loadingProgressIndicator.isIndeterminate = false
- if (it == emulationViewModel.totalShaders.value) {
- binding.loadingText.setText(R.string.loading)
- binding.loadingProgressIndicator.isIndeterminate = true
- }
- }
+ if (it < binding.loadingProgressIndicator.max) {
+ binding.loadingProgressIndicator.progress = it
}
}
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- emulationViewModel.totalShaders.collectLatest {
- binding.loadingProgressIndicator.max = it
- }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- emulationViewModel.shaderMessage.collectLatest {
- if (it.isNotEmpty()) {
- binding.loadingText.text = it
- }
- }
- }
+
+ if (it == emulationViewModel.totalShaders.value) {
+ binding.loadingText.setText(R.string.loading)
+ binding.loadingProgressIndicator.isIndeterminate = true
}
- launch {
- repeatOnLifecycle(Lifecycle.State.RESUMED) {
- driverViewModel.isInteractionAllowed.collect {
- if (it) {
- startEmulation()
- }
- }
- }
+ }
+ emulationViewModel.totalShaders.collect(viewLifecycleOwner) {
+ binding.loadingProgressIndicator.max = it
+ }
+ emulationViewModel.shaderMessage.collect(viewLifecycleOwner) {
+ if (it.isNotEmpty()) {
+ binding.loadingText.text = it
}
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- emulationViewModel.emulationStarted.collectLatest {
- if (it) {
- binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt())
- ViewUtils.showView(binding.surfaceInputOverlay)
- ViewUtils.hideView(binding.loadingIndicator)
-
- emulationState.updateSurface()
-
- // Setup overlays
- updateShowFpsOverlay()
- updateThermalOverlay()
- }
- }
- }
+ }
+
+ emulationViewModel.emulationStarted.collect(viewLifecycleOwner) {
+ if (it) {
+ binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt())
+ ViewUtils.showView(binding.surfaceInputOverlay)
+ ViewUtils.hideView(binding.loadingIndicator)
+
+ emulationState.updateSurface()
+
+ // Setup overlays
+ updateShowFpsOverlay()
+ updateThermalOverlay()
}
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- emulationViewModel.isEmulationStopping.collectLatest {
- if (it) {
- binding.loadingText.setText(R.string.shutting_down)
- ViewUtils.showView(binding.loadingIndicator)
- ViewUtils.hideView(binding.inputContainer)
- ViewUtils.hideView(binding.showFpsText)
- }
- }
- }
+ }
+ emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) {
+ if (it) {
+ binding.loadingText.setText(R.string.shutting_down)
+ ViewUtils.showView(binding.loadingIndicator)
+ ViewUtils.hideView(binding.inputContainer)
+ ViewUtils.hideView(binding.showFpsText)
}
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- emulationViewModel.drawerOpen.collect {
- if (it) {
- binding.drawerLayout.open()
- binding.inGameMenu.requestFocus()
- } else {
- binding.drawerLayout.close()
- }
- }
- }
+ }
+ emulationViewModel.drawerOpen.collect(viewLifecycleOwner) {
+ if (it) {
+ binding.drawerLayout.open()
+ binding.inGameMenu.requestFocus()
+ } else {
+ binding.drawerLayout.close()
}
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- emulationViewModel.programChanged.collect {
- if (it != 0) {
- emulationViewModel.setEmulationStarted(false)
- binding.drawerLayout.close()
- binding.drawerLayout
- .setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
- ViewUtils.hideView(binding.surfaceInputOverlay)
- ViewUtils.showView(binding.loadingIndicator)
- }
- }
- }
+ }
+ emulationViewModel.programChanged.collect(viewLifecycleOwner) {
+ if (it != 0) {
+ emulationViewModel.setEmulationStarted(false)
+ binding.drawerLayout.close()
+ binding.drawerLayout
+ .setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
+ ViewUtils.hideView(binding.surfaceInputOverlay)
+ ViewUtils.showView(binding.loadingIndicator)
}
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- emulationViewModel.emulationStopped.collect {
- if (it && emulationViewModel.programChanged.value != -1) {
- if (perfStatsUpdater != null) {
- perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
- }
- emulationState.changeProgram(emulationViewModel.programChanged.value)
- emulationViewModel.setProgramChanged(-1)
- emulationViewModel.setEmulationStopped(false)
- }
- }
+ }
+ emulationViewModel.emulationStopped.collect(viewLifecycleOwner) {
+ if (it && emulationViewModel.programChanged.value != -1) {
+ if (perfStatsUpdater != null) {
+ perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
}
+ emulationState.changeProgram(emulationViewModel.programChanged.value)
+ emulationViewModel.setProgramChanged(-1)
+ emulationViewModel.setEmulationStopped(false)
}
}
+
+ driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) {
+ if (it) startEmulation()
+ }
}
private fun startEmulation(programIndex: Int = 0) {
@@ -491,14 +442,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.drawerLayout.close()
}
if (showInputOverlay) {
- binding.surfaceInputOverlay.visibility = View.INVISIBLE
+ binding.surfaceInputOverlay.setVisible(visible = false, gone = false)
}
} else {
- if (showInputOverlay && emulationViewModel.emulationStarted.value) {
- binding.surfaceInputOverlay.visibility = View.VISIBLE
- } else {
- binding.surfaceInputOverlay.visibility = View.INVISIBLE
- }
+ binding.surfaceInputOverlay.setVisible(
+ showInputOverlay && emulationViewModel.emulationStarted.value
+ )
if (!isInFoldableLayout) {
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
binding.surfaceInputOverlay.layout = OverlayLayout.Portrait
@@ -535,7 +484,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
private fun updateShowFpsOverlay() {
- if (BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()) {
+ val showOverlay = BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()
+ binding.showFpsText.setVisible(showOverlay)
+ if (showOverlay) {
val SYSTEM_FPS = 0
val FPS = 1
val FRAMETIME = 2
@@ -555,17 +506,17 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
perfStatsUpdateHandler.post(perfStatsUpdater!!)
- binding.showFpsText.visibility = View.VISIBLE
} else {
if (perfStatsUpdater != null) {
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
}
- binding.showFpsText.visibility = View.GONE
}
}
private fun updateThermalOverlay() {
- if (BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean()) {
+ val showOverlay = BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean()
+ binding.showThermalsText.setVisible(showOverlay)
+ if (showOverlay) {
thermalStatsUpdater = {
if (emulationViewModel.emulationStarted.value &&
!emulationViewModel.isEmulationStopping.value
@@ -587,12 +538,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
thermalStatsUpdateHandler.post(thermalStatsUpdater!!)
- binding.showThermalsText.visibility = View.VISIBLE
} else {
if (thermalStatsUpdater != null) {
thermalStatsUpdateHandler.removeCallbacks(thermalStatsUpdater!!)
}
- binding.showThermalsText.visibility = View.GONE
}
}
@@ -861,12 +810,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
}
- binding.doneControlConfig.visibility = View.VISIBLE
+ binding.doneControlConfig.setVisible(true)
binding.surfaceInputOverlay.setIsInEditMode(true)
}
private fun stopConfiguringControls() {
- binding.doneControlConfig.visibility = View.GONE
+ binding.doneControlConfig.setVisible(false)
binding.surfaceInputOverlay.setIsInEditMode(false)
// Unlock the orientation if it was locked for editing
if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == EmulationOrientation.Unspecified.int) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt
index 5c558b1a5..3a6f7a38c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt
@@ -13,9 +13,6 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
@@ -27,6 +24,7 @@ import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
+import org.yuzu.yuzu_emu.utils.collect
class GameFoldersFragment : Fragment() {
private var _binding: FragmentFoldersBinding? = null
@@ -70,12 +68,8 @@ class GameFoldersFragment : Fragment() {
adapter = FolderAdapter(requireActivity(), gamesViewModel)
}
- viewLifecycleOwner.lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- gamesViewModel.folders.collect {
- (binding.listFolders.adapter as FolderAdapter).submitList(it)
- }
- }
+ gamesViewModel.folders.collect(viewLifecycleOwner) {
+ (binding.listFolders.adapter as FolderAdapter).submitList(it)
}
val mainActivity = requireActivity() as MainActivity
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt
index dbd56e84f..97a8954bb 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt
@@ -27,6 +27,7 @@ import org.yuzu.yuzu_emu.databinding.FragmentGameInfoBinding
import org.yuzu.yuzu_emu.model.GameVerificationResult
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.GameMetadata
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
class GameInfoFragment : Fragment() {
@@ -85,7 +86,7 @@ class GameInfoFragment : Fragment() {
copyToClipboard(getString(R.string.developer), args.game.developer)
}
} else {
- developer.visibility = View.GONE
+ developer.setVisible(false)
}
version.setHint(R.string.version)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt
index 3ea5e16ca..c06842c59 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt
@@ -3,11 +3,9 @@
package org.yuzu.yuzu_emu.fragments
-import android.annotation.SuppressLint
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.os.Bundle
-import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -18,9 +16,7 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
@@ -46,7 +42,9 @@ import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GameIconUtils
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.MemoryUtil
+import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
+import org.yuzu.yuzu_emu.utils.collect
import java.io.BufferedOutputStream
import java.io.File
@@ -76,8 +74,6 @@ class GamePropertiesFragment : Fragment() {
return binding.root
}
- // This is using the correct scope, lint is just acting up
- @SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = true)
@@ -107,13 +103,7 @@ class GamePropertiesFragment : Fragment() {
GameIconUtils.loadGameIcon(args.game, binding.imageGameScreen)
binding.title.text = args.game.title
- binding.title.postDelayed(
- {
- binding.title.ellipsize = TextUtils.TruncateAt.MARQUEE
- binding.title.isSelected = true
- },
- 3000
- )
+ binding.title.marquee()
binding.buttonStart.setOnClickListener {
LaunchGameDialogFragment.newInstance(args.game)
@@ -122,28 +112,14 @@ class GamePropertiesFragment : Fragment() {
reloadList()
- viewLifecycleOwner.lifecycleScope.apply {
- launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- homeViewModel.openImportSaves.collect {
- if (it) {
- importSaves.launch(arrayOf("application/zip"))
- homeViewModel.setOpenImportSaves(false)
- }
- }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- homeViewModel.reloadPropertiesList.collect {
- if (it) {
- reloadList()
- homeViewModel.reloadPropertiesList(false)
- }
- }
- }
- }
- }
+ homeViewModel.openImportSaves.collect(
+ viewLifecycleOwner,
+ resetState = { homeViewModel.setOpenImportSaves(false) }
+ ) { if (it) importSaves.launch(arrayOf("application/zip")) }
+ homeViewModel.reloadPropertiesList.collect(
+ viewLifecycleOwner,
+ resetState = { homeViewModel.reloadPropertiesList(false) }
+ ) { if (it) reloadList() }
setInsets()
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index 87e130d3e..14a2504b6 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -91,6 +91,20 @@ class HomeSettingsFragment : Fragment() {
)
add(
HomeSetting(
+ R.string.preferences_controls,
+ R.string.preferences_controls_description,
+ R.drawable.ic_controller,
+ {
+ val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+ null,
+ Settings.MenuTag.SECTION_INPUT
+ )
+ binding.root.findNavController().navigate(action)
+ }
+ )
+ )
+ add(
+ HomeSetting(
R.string.gpu_driver_manager,
R.string.install_gpu_driver_description,
R.drawable.ic_build,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
index 63112dc6f..d218da1c8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
@@ -14,9 +14,6 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
@@ -35,6 +32,7 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
+import org.yuzu.yuzu_emu.utils.collect
import java.io.BufferedOutputStream
import java.io.File
import java.math.BigInteger
@@ -75,14 +73,10 @@ class InstallableFragment : Fragment() {
binding.root.findNavController().popBackStack()
}
- viewLifecycleOwner.lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- homeViewModel.openImportSaves.collect {
- if (it) {
- importSaves.launch(arrayOf("application/zip"))
- homeViewModel.setOpenImportSaves(false)
- }
- }
+ homeViewModel.openImportSaves.collect(viewLifecycleOwner) {
+ if (it) {
+ importSaves.launch(arrayOf("application/zip"))
+ homeViewModel.setOpenImportSaves(false)
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt
index d201cb80c..ee3bb0386 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt
@@ -13,15 +13,13 @@ import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.TaskViewModel
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
+import org.yuzu.yuzu_emu.utils.collect
class ProgressDialogFragment : DialogFragment() {
private val taskViewModel: TaskViewModel by activityViewModels()
@@ -64,72 +62,50 @@ class ProgressDialogFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.message.isSelected = true
- viewLifecycleOwner.lifecycleScope.apply {
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- taskViewModel.isComplete.collect {
- if (it) {
- dismiss()
- when (val result = taskViewModel.result.value) {
- is String -> Toast.makeText(
- requireContext(),
- result,
- Toast.LENGTH_LONG
- ).show()
-
- is MessageDialogFragment -> result.show(
- requireActivity().supportFragmentManager,
- MessageDialogFragment.TAG
- )
-
- else -> {
- // Do nothing
- }
- }
- taskViewModel.clear()
- }
+ taskViewModel.isComplete.collect(viewLifecycleOwner) {
+ if (it) {
+ dismiss()
+ when (val result = taskViewModel.result.value) {
+ is String -> Toast.makeText(
+ requireContext(),
+ result,
+ Toast.LENGTH_LONG
+ ).show()
+
+ is MessageDialogFragment -> result.show(
+ requireActivity().supportFragmentManager,
+ MessageDialogFragment.TAG
+ )
+
+ else -> {
+ // Do nothing
}
}
+ taskViewModel.clear()
}
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- taskViewModel.cancelled.collect {
- if (it) {
- dialog?.setTitle(R.string.cancelling)
- }
- }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- taskViewModel.progress.collect {
- if (it != 0.0) {
- binding.progressBar.apply {
- isIndeterminate = false
- progress = (
- (it / taskViewModel.maxProgress.value) *
- PROGRESS_BAR_RESOLUTION
- ).toInt()
- min = 0
- max = PROGRESS_BAR_RESOLUTION
- }
- }
- }
- }
+ }
+ taskViewModel.cancelled.collect(viewLifecycleOwner) {
+ if (it) {
+ dialog?.setTitle(R.string.cancelling)
}
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- taskViewModel.message.collect {
- if (it.isEmpty()) {
- binding.message.visibility = View.GONE
- } else {
- binding.message.visibility = View.VISIBLE
- binding.message.text = it
- }
- }
+ }
+ taskViewModel.progress.collect(viewLifecycleOwner) {
+ if (it != 0.0) {
+ binding.progressBar.apply {
+ isIndeterminate = false
+ progress = (
+ (it / taskViewModel.maxProgress.value) *
+ PROGRESS_BAR_RESOLUTION
+ ).toInt()
+ min = 0
+ max = PROGRESS_BAR_RESOLUTION
}
}
}
+ taskViewModel.message.collect(viewLifecycleOwner) {
+ binding.message.setVisible(it.isNotEmpty())
+ binding.message.text = it
+ }
}
// By default, the ProgressDialog will immediately dismiss itself upon a button being pressed.
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
index 20b10b1a0..662ae9760 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
@@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.fragments
-import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
@@ -18,14 +17,9 @@ import androidx.core.view.updatePadding
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import androidx.preference.PreferenceManager
import info.debatty.java.stringsimilarity.Jaccard
import info.debatty.java.stringsimilarity.JaroWinkler
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
import java.util.Locale
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
@@ -35,6 +29,8 @@ import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
+import org.yuzu.yuzu_emu.utils.collect
class SearchFragment : Fragment() {
private var _binding: FragmentSearchBinding? = null
@@ -58,8 +54,6 @@ class SearchFragment : Fragment() {
return binding.root
}
- // This is using the correct scope, lint is just acting up
- @SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = true, animated = true)
@@ -81,42 +75,18 @@ class SearchFragment : Fragment() {
binding.chipGroup.setOnCheckedStateChangeListener { _, _ -> filterAndSearch() }
binding.searchText.doOnTextChanged { text: CharSequence?, _: Int, _: Int, _: Int ->
- if (text.toString().isNotEmpty()) {
- binding.clearButton.visibility = View.VISIBLE
- } else {
- binding.clearButton.visibility = View.INVISIBLE
- }
+ binding.clearButton.setVisible(text.toString().isNotEmpty())
filterAndSearch()
}
- viewLifecycleOwner.lifecycleScope.apply {
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- gamesViewModel.searchFocused.collect {
- if (it) {
- focusSearch()
- gamesViewModel.setSearchFocused(false)
- }
- }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- gamesViewModel.games.collectLatest { filterAndSearch() }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- gamesViewModel.searchedGames.collect {
- (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
- if (it.isEmpty()) {
- binding.noResultsView.visibility = View.VISIBLE
- } else {
- binding.noResultsView.visibility = View.GONE
- }
- }
- }
- }
+ gamesViewModel.searchFocused.collect(
+ viewLifecycleOwner,
+ resetState = { gamesViewModel.setSearchFocused(false) }
+ ) { if (it) focusSearch() }
+ gamesViewModel.games.collect(viewLifecycleOwner) { filterAndSearch() }
+ gamesViewModel.searchedGames.collect(viewLifecycleOwner) {
+ (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
+ binding.noResultsView.setVisible(it.isNotEmpty())
}
binding.clearButton.setOnClickListener { binding.searchText.setText("") }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
index ebf41a639..4f7548e98 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
@@ -4,7 +4,6 @@
package org.yuzu.yuzu_emu.fragments
import android.Manifest
-import android.annotation.SuppressLint
import android.content.Intent
import android.os.Build
import android.os.Bundle
@@ -23,9 +22,6 @@ import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.preference.PreferenceManager
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
@@ -46,6 +42,8 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.ViewUtils
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
+import org.yuzu.yuzu_emu.utils.collect
class SetupFragment : Fragment() {
private var _binding: FragmentSetupBinding? = null
@@ -77,8 +75,6 @@ class SetupFragment : Fragment() {
return binding.root
}
- // This is using the correct scope, lint is just acting up
- @SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mainActivity = requireActivity() as MainActivity
@@ -210,28 +206,14 @@ class SetupFragment : Fragment() {
)
}
- viewLifecycleOwner.lifecycleScope.apply {
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- homeViewModel.shouldPageForward.collect {
- if (it) {
- pageForward()
- homeViewModel.setShouldPageForward(false)
- }
- }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- homeViewModel.gamesDirSelected.collect {
- if (it) {
- gamesDirCallback.onStepCompleted()
- homeViewModel.setGamesDirSelected(false)
- }
- }
- }
- }
- }
+ homeViewModel.shouldPageForward.collect(
+ viewLifecycleOwner,
+ resetState = { homeViewModel.setShouldPageForward(false) }
+ ) { if (it) pageForward() }
+ homeViewModel.gamesDirSelected.collect(
+ viewLifecycleOwner,
+ resetState = { homeViewModel.setGamesDirSelected(false) }
+ ) { if (it) gamesDirCallback.onStepCompleted() }
binding.viewPager2.apply {
adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages)
@@ -292,12 +274,8 @@ class SetupFragment : Fragment() {
val backIsVisible = savedInstanceState.getBoolean(KEY_BACK_VISIBILITY)
hasBeenWarned = savedInstanceState.getBooleanArray(KEY_HAS_BEEN_WARNED)!!
- if (nextIsVisible) {
- binding.buttonNext.visibility = View.VISIBLE
- }
- if (backIsVisible) {
- binding.buttonBack.visibility = View.VISIBLE
- }
+ binding.buttonNext.setVisible(nextIsVisible)
+ binding.buttonBack.setVisible(backIsVisible)
} else {
hasBeenWarned = BooleanArray(pages.size)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
index c87486c90..ed112a38d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
@@ -24,10 +24,10 @@ import androidx.core.content.ContextCompat
import androidx.window.layout.WindowMetricsCalculator
import kotlin.math.max
import kotlin.math.min
-import org.yuzu.yuzu_emu.NativeLibrary
-import org.yuzu.yuzu_emu.NativeLibrary.ButtonType
-import org.yuzu.yuzu_emu.NativeLibrary.StickType
+import org.yuzu.yuzu_emu.features.input.NativeInput
import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
+import org.yuzu.yuzu_emu.features.input.model.NativeButton
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.overlay.model.OverlayControl
@@ -100,19 +100,19 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
var shouldUpdateView = false
val playerIndex =
- if (NativeLibrary.isHandheldOnly()) {
- NativeLibrary.ConsoleDevice
+ if (NativeInput.isHandheldOnly()) {
+ NativeInput.ConsoleDevice
} else {
- NativeLibrary.Player1Device
+ NativeInput.Player1Device
}
for (button in overlayButtons) {
if (!button.updateStatus(event)) {
continue
}
- NativeLibrary.onGamePadButtonEvent(
+ NativeInput.onOverlayButtonEvent(
playerIndex,
- button.buttonId,
+ button.button,
button.status
)
playHaptics(event)
@@ -123,24 +123,24 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
if (!dpad.updateStatus(event, BooleanSetting.DPAD_SLIDE.getBoolean())) {
continue
}
- NativeLibrary.onGamePadButtonEvent(
+ NativeInput.onOverlayButtonEvent(
playerIndex,
- dpad.upId,
+ dpad.up,
dpad.upStatus
)
- NativeLibrary.onGamePadButtonEvent(
+ NativeInput.onOverlayButtonEvent(
playerIndex,
- dpad.downId,
+ dpad.down,
dpad.downStatus
)
- NativeLibrary.onGamePadButtonEvent(
+ NativeInput.onOverlayButtonEvent(
playerIndex,
- dpad.leftId,
+ dpad.left,
dpad.leftStatus
)
- NativeLibrary.onGamePadButtonEvent(
+ NativeInput.onOverlayButtonEvent(
playerIndex,
- dpad.rightId,
+ dpad.right,
dpad.rightStatus
)
playHaptics(event)
@@ -151,16 +151,15 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
if (!joystick.updateStatus(event)) {
continue
}
- val axisID = joystick.joystickId
- NativeLibrary.onGamePadJoystickEvent(
+ NativeInput.onOverlayJoystickEvent(
playerIndex,
- axisID,
+ joystick.joystick,
joystick.xAxis,
joystick.realYAxis
)
- NativeLibrary.onGamePadButtonEvent(
+ NativeInput.onOverlayButtonEvent(
playerIndex,
- joystick.buttonId,
+ joystick.button,
joystick.buttonStatus
)
playHaptics(event)
@@ -187,7 +186,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP
if (isActionDown && !isTouchInputConsumed(pointerId)) {
- NativeLibrary.onTouchPressed(pointerId, xPosition.toFloat(), yPosition.toFloat())
+ NativeInput.onTouchPressed(pointerId, xPosition.toFloat(), yPosition.toFloat())
}
if (isActionMove) {
@@ -196,12 +195,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
if (isTouchInputConsumed(fingerId)) {
continue
}
- NativeLibrary.onTouchMoved(fingerId, event.getX(i), event.getY(i))
+ NativeInput.onTouchMoved(fingerId, event.getX(i), event.getY(i))
}
}
if (isActionUp && !isTouchInputConsumed(pointerId)) {
- NativeLibrary.onTouchReleased(pointerId)
+ NativeInput.onTouchReleased(pointerId)
}
return true
@@ -359,7 +358,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
windowSize,
R.drawable.facebutton_a,
R.drawable.facebutton_a_depressed,
- ButtonType.BUTTON_A,
+ NativeButton.A,
data,
position
)
@@ -373,7 +372,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
windowSize,
R.drawable.facebutton_b,
R.drawable.facebutton_b_depressed,
- ButtonType.BUTTON_B,
+ NativeButton.B,
data,
position
)
@@ -387,7 +386,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
windowSize,
R.drawable.facebutton_x,
R.drawable.facebutton_x_depressed,
- ButtonType.BUTTON_X,
+ NativeButton.X,
data,
position
)
@@ -401,7 +400,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
windowSize,
R.drawable.facebutton_y,
R.drawable.facebutton_y_depressed,
- ButtonType.BUTTON_Y,
+ NativeButton.Y,
data,
position
)
@@ -415,7 +414,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
windowSize,
R.drawable.facebutton_plus,
R.drawable.facebutton_plus_depressed,
- ButtonType.BUTTON_PLUS,
+ NativeButton.Plus,
data,
position
)
@@ -429,7 +428,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
windowSize,
R.drawable.facebutton_minus,
R.drawable.facebutton_minus_depressed,
- ButtonType.BUTTON_MINUS,
+ NativeButton.Minus,
data,
position
)
@@ -443,7 +442,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
windowSize,
R.drawable.facebutton_home,
R.drawable.facebutton_home_depressed,
- ButtonType.BUTTON_HOME,
+ NativeButton.Home,
data,
position
)
@@ -457,7 +456,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
windowSize,
R.drawable.facebutton_screenshot,
R.drawable.facebutton_screenshot_depressed,
- ButtonType.BUTTON_CAPTURE,
+ NativeButton.Capture,
data,
position
)
@@ -471,7 +470,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
windowSize,
R.drawable.l_shoulder,
R.drawable.l_shoulder_depressed,
- ButtonType.TRIGGER_L,
+ NativeButton.L,
data,
position
)
@@ -485,7 +484,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
windowSize,
R.drawable.r_shoulder,
R.drawable.r_shoulder_depressed,
- ButtonType.TRIGGER_R,
+ NativeButton.R,
data,
position
)
@@ -499,7 +498,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
windowSize,
R.drawable.zl_trigger,
R.drawable.zl_trigger_depressed,
- ButtonType.TRIGGER_ZL,
+ NativeButton.ZL,
data,
position
)
@@ -513,7 +512,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
windowSize,
R.drawable.zr_trigger,
R.drawable.zr_trigger_depressed,
- ButtonType.TRIGGER_ZR,
+ NativeButton.ZR,
data,
position
)
@@ -527,7 +526,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
windowSize,
R.drawable.button_l3,
R.drawable.button_l3_depressed,
- ButtonType.STICK_L,
+ NativeButton.LStick,
data,
position
)
@@ -541,7 +540,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
windowSize,
R.drawable.button_r3,
R.drawable.button_r3_depressed,
- ButtonType.STICK_R,
+ NativeButton.RStick,
data,
position
)
@@ -556,8 +555,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
R.drawable.joystick_range,
R.drawable.joystick,
R.drawable.joystick_depressed,
- StickType.STICK_L,
- ButtonType.STICK_L,
+ NativeAnalog.LStick,
+ NativeButton.LStick,
data,
position
)
@@ -572,8 +571,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
R.drawable.joystick_range,
R.drawable.joystick,
R.drawable.joystick_depressed,
- StickType.STICK_R,
- ButtonType.STICK_R,
+ NativeAnalog.RStick,
+ NativeButton.RStick,
data,
position
)
@@ -665,7 +664,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
val overlayControlData = NativeConfig.getOverlayControlData()
overlayControlData.forEach {
- it.enabled = OverlayControl.from(it.id)?.defaultVisibility == false
+ it.enabled = OverlayControl.from(it.id)?.defaultVisibility == true
}
NativeConfig.setOverlayControlData(overlayControlData)
@@ -835,7 +834,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
windowSize: Pair<Point, Point>,
defaultResId: Int,
pressedResId: Int,
- buttonId: Int,
+ button: NativeButton,
overlayControlData: OverlayControlData,
position: Pair<Double, Double>
): InputOverlayDrawableButton {
@@ -869,7 +868,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
res,
defaultStateBitmap,
pressedStateBitmap,
- buttonId,
+ button,
overlayControlData
)
@@ -940,11 +939,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
res,
defaultStateBitmap,
pressedOneDirectionStateBitmap,
- pressedTwoDirectionsStateBitmap,
- ButtonType.DPAD_UP,
- ButtonType.DPAD_DOWN,
- ButtonType.DPAD_LEFT,
- ButtonType.DPAD_RIGHT
+ pressedTwoDirectionsStateBitmap
)
// Get the minimum and maximum coordinates of the screen where the button can be placed.
@@ -993,8 +988,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
resOuter: Int,
defaultResInner: Int,
pressedResInner: Int,
- joystick: Int,
- buttonId: Int,
+ joystick: NativeAnalog,
+ button: NativeButton,
overlayControlData: OverlayControlData,
position: Pair<Double, Double>
): InputOverlayDrawableJoystick {
@@ -1042,7 +1037,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
outerRect,
innerRect,
joystick,
- buttonId,
+ button,
overlayControlData.id
)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
index b14a4f96e..fee3d04ee 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
@@ -9,7 +9,8 @@ import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.view.MotionEvent
-import org.yuzu.yuzu_emu.NativeLibrary.ButtonState
+import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState
+import org.yuzu.yuzu_emu.features.input.model.NativeButton
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
/**
@@ -19,13 +20,13 @@ import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
* @param res [Resources] instance.
* @param defaultStateBitmap [Bitmap] to use with the default state Drawable.
* @param pressedStateBitmap [Bitmap] to use with the pressed state Drawable.
- * @param buttonId Identifier for this type of button.
+ * @param button [NativeButton] for this type of button.
*/
class InputOverlayDrawableButton(
res: Resources,
defaultStateBitmap: Bitmap,
pressedStateBitmap: Bitmap,
- val buttonId: Int,
+ val button: NativeButton,
val overlayControlData: OverlayControlData
) {
// The ID value what motion event is tracking
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt
index 8aef6f5a5..0cb6ff244 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt
@@ -9,7 +9,8 @@ import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.view.MotionEvent
-import org.yuzu.yuzu_emu.NativeLibrary.ButtonState
+import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState
+import org.yuzu.yuzu_emu.features.input.model.NativeButton
/**
* Custom [BitmapDrawable] that is capable
@@ -19,20 +20,12 @@ import org.yuzu.yuzu_emu.NativeLibrary.ButtonState
* @param defaultStateBitmap [Bitmap] of the default state.
* @param pressedOneDirectionStateBitmap [Bitmap] of the pressed state in one direction.
* @param pressedTwoDirectionsStateBitmap [Bitmap] of the pressed state in two direction.
- * @param buttonUp Identifier for the up button.
- * @param buttonDown Identifier for the down button.
- * @param buttonLeft Identifier for the left button.
- * @param buttonRight Identifier for the right button.
*/
class InputOverlayDrawableDpad(
res: Resources,
defaultStateBitmap: Bitmap,
pressedOneDirectionStateBitmap: Bitmap,
- pressedTwoDirectionsStateBitmap: Bitmap,
- buttonUp: Int,
- buttonDown: Int,
- buttonLeft: Int,
- buttonRight: Int
+ pressedTwoDirectionsStateBitmap: Bitmap
) {
/**
* Gets one of the InputOverlayDrawableDpad's button IDs.
@@ -40,10 +33,10 @@ class InputOverlayDrawableDpad(
* @return the requested InputOverlayDrawableDpad's button ID.
*/
// The ID identifying what type of button this Drawable represents.
- val upId: Int
- val downId: Int
- val leftId: Int
- val rightId: Int
+ val up = NativeButton.DUp
+ val down = NativeButton.DDown
+ val left = NativeButton.DLeft
+ val right = NativeButton.DRight
var trackId: Int
val width: Int
@@ -69,10 +62,6 @@ class InputOverlayDrawableDpad(
this.pressedTwoDirectionsStateBitmap = BitmapDrawable(res, pressedTwoDirectionsStateBitmap)
width = this.defaultStateBitmap.intrinsicWidth
height = this.defaultStateBitmap.intrinsicHeight
- upId = buttonUp
- downId = buttonDown
- leftId = buttonLeft
- rightId = buttonRight
trackId = -1
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
index 113bf7c24..4b07107fc 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
@@ -13,7 +13,9 @@ import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
-import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState
+import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
+import org.yuzu.yuzu_emu.features.input.model.NativeButton
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
/**
@@ -26,8 +28,8 @@ import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
* @param bitmapInnerPressed [Bitmap] which represents the pressed inner movable part of the joystick.
* @param rectOuter [Rect] which represents the outer joystick bounds.
* @param rectInner [Rect] which represents the inner joystick bounds.
- * @param joystickId The ID value what type of joystick this Drawable represents.
- * @param buttonId The ID value what type of button this Drawable represents.
+ * @param joystick The [NativeAnalog] this Drawable represents.
+ * @param button The [NativeButton] this Drawable represents.
*/
class InputOverlayDrawableJoystick(
res: Resources,
@@ -36,8 +38,8 @@ class InputOverlayDrawableJoystick(
bitmapInnerPressed: Bitmap,
rectOuter: Rect,
rectInner: Rect,
- val joystickId: Int,
- val buttonId: Int,
+ val joystick: NativeAnalog,
+ val button: NativeButton,
val prefId: String
) {
// The ID value what motion event is tracking
@@ -69,8 +71,7 @@ class InputOverlayDrawableJoystick(
// TODO: Add button support
val buttonStatus: Int
- get() =
- NativeLibrary.ButtonState.RELEASED
+ get() = ButtonState.RELEASED
var bounds: Rect
get() = outerBitmap.bounds
set(bounds) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
index 23ca49b53..fadb20e39 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
@@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.ui
-import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -14,19 +13,16 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.color.MaterialColors
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.GameAdapter
import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
+import org.yuzu.yuzu_emu.utils.collect
class GamesFragment : Fragment() {
private var _binding: FragmentGamesBinding? = null
@@ -44,8 +40,6 @@ class GamesFragment : Fragment() {
return binding.root
}
- // This is using the correct scope, lint is just acting up
- @SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = true, animated = true)
@@ -88,49 +82,28 @@ class GamesFragment : Fragment() {
}
}
- viewLifecycleOwner.lifecycleScope.apply {
- launch {
- repeatOnLifecycle(Lifecycle.State.RESUMED) {
- gamesViewModel.isReloading.collect {
- binding.swipeRefresh.isRefreshing = it
- if (gamesViewModel.games.value.isEmpty() && !it) {
- binding.noticeText.visibility = View.VISIBLE
- } else {
- binding.noticeText.visibility = View.INVISIBLE
- }
- }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.RESUMED) {
- gamesViewModel.games.collectLatest {
- (binding.gridGames.adapter as GameAdapter).submitList(it)
- }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.RESUMED) {
- gamesViewModel.shouldSwapData.collect {
- if (it) {
- (binding.gridGames.adapter as GameAdapter).submitList(
- gamesViewModel.games.value
- )
- gamesViewModel.setShouldSwapData(false)
- }
- }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.RESUMED) {
- gamesViewModel.shouldScrollToTop.collect {
- if (it) {
- scrollToTop()
- gamesViewModel.setShouldScrollToTop(false)
- }
- }
- }
+ gamesViewModel.isReloading.collect(viewLifecycleOwner) {
+ binding.swipeRefresh.isRefreshing = it
+ binding.noticeText.setVisible(
+ visible = gamesViewModel.games.value.isEmpty() && !it,
+ gone = false
+ )
+ }
+ gamesViewModel.games.collect(viewLifecycleOwner) {
+ (binding.gridGames.adapter as GameAdapter).submitList(it)
+ }
+ gamesViewModel.shouldSwapData.collect(
+ viewLifecycleOwner,
+ resetState = { gamesViewModel.setShouldSwapData(false) }
+ ) {
+ if (it) {
+ (binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value)
}
}
+ gamesViewModel.shouldScrollToTop.collect(
+ viewLifecycleOwner,
+ resetState = { gamesViewModel.setShouldScrollToTop(false) }
+ ) { if (it) scrollToTop() }
setInsets()
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index 4df4ac4c6..757463a0b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -19,9 +19,6 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
@@ -30,7 +27,6 @@ import com.google.android.material.color.MaterialColors
import com.google.android.material.navigation.NavigationBarView
import java.io.File
import java.io.FilenameFilter
-import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
@@ -47,6 +43,7 @@ import org.yuzu.yuzu_emu.model.InstallResult
import org.yuzu.yuzu_emu.model.TaskState
import org.yuzu.yuzu_emu.model.TaskViewModel
import org.yuzu.yuzu_emu.utils.*
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.util.zip.ZipEntry
@@ -139,42 +136,23 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
// Prevents navigation from being drawn for a short time on recreation if set to hidden
if (!homeViewModel.navigationVisible.value.first) {
- binding.navigationView.visibility = View.INVISIBLE
- binding.statusBarShade.visibility = View.INVISIBLE
+ binding.navigationView.setVisible(visible = false, gone = false)
+ binding.statusBarShade.setVisible(visible = false, gone = false)
}
- lifecycleScope.apply {
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- homeViewModel.contentToInstall.collect {
- if (it != null) {
- installContent(it)
- homeViewModel.setContentToInstall(null)
- }
- }
- }
- }
- launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- homeViewModel.checkKeys.collect {
- if (it) {
- checkKeys()
- homeViewModel.setCheckKeys(false)
- }
- }
- }
+ homeViewModel.navigationVisible.collect(this) { showNavigation(it.first, it.second) }
+ homeViewModel.statusBarShadeVisible.collect(this) { showStatusBarShade(it) }
+ homeViewModel.contentToInstall.collect(
+ this,
+ resetState = { homeViewModel.setContentToInstall(null) }
+ ) {
+ if (it != null) {
+ installContent(it)
}
}
+ homeViewModel.checkKeys.collect(this, resetState = { homeViewModel.setCheckKeys(false) }) {
+ if (it) checkKeys()
+ }
setInsets()
}
@@ -214,18 +192,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
private fun showNavigation(visible: Boolean, animated: Boolean) {
if (!animated) {
- if (visible) {
- binding.navigationView.visibility = View.VISIBLE
- } else {
- binding.navigationView.visibility = View.INVISIBLE
- }
+ binding.navigationView.setVisible(visible)
return
}
val smallLayout = resources.getBoolean(R.bool.small_layout)
binding.navigationView.animate().apply {
if (visible) {
- binding.navigationView.visibility = View.VISIBLE
+ binding.navigationView.setVisible(true)
duration = 300
interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f)
@@ -264,7 +238,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}.withEndAction {
if (!visible) {
- binding.navigationView.visibility = View.INVISIBLE
+ binding.navigationView.setVisible(visible = false, gone = false)
}
}.start()
}
@@ -272,7 +246,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
private fun showStatusBarShade(visible: Boolean) {
binding.statusBarShade.animate().apply {
if (visible) {
- binding.statusBarShade.visibility = View.VISIBLE
+ binding.statusBarShade.setVisible(true)
binding.statusBarShade.translationY = binding.statusBarShade.height.toFloat() * -2
duration = 300
translationY(0f)
@@ -284,7 +258,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}.withEndAction {
if (!visible) {
- binding.statusBarShade.visibility = View.INVISIBLE
+ binding.statusBarShade.setVisible(visible = false, gone = false)
}
}.start()
}
@@ -524,7 +498,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
this@MainActivity,
titleId = R.string.content_install_notice,
descriptionId = R.string.content_install_notice_description,
- positiveAction = { homeViewModel.setContentToInstall(documents) }
+ positiveAction = { homeViewModel.setContentToInstall(documents) },
+ negativeAction = {}
)
}
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt
index e63382e1d..2c7356e6a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt
@@ -6,439 +6,89 @@ package org.yuzu.yuzu_emu.utils
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
-import kotlin.math.sqrt
-import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.features.input.NativeInput
+import org.yuzu.yuzu_emu.features.input.YuzuInputOverlayDevice
+import org.yuzu.yuzu_emu.features.input.YuzuPhysicalDevice
object InputHandler {
- private var controllerIds = getGameControllerIds()
-
- fun initialize() {
- // Connect first controller
- NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device))
- }
-
- fun updateControllerIds() {
- controllerIds = getGameControllerIds()
- }
+ var androidControllers = mapOf<Int, YuzuPhysicalDevice>()
+ var registeredControllers = mutableListOf<ParamPackage>()
fun dispatchKeyEvent(event: KeyEvent): Boolean {
- val button: Int = when (event.device.vendorId) {
- 0x045E -> getInputXboxButtonKey(event.keyCode)
- 0x054C -> getInputDS5ButtonKey(event.keyCode)
- 0x057E -> getInputJoyconButtonKey(event.keyCode)
- 0x1532 -> getInputRazerButtonKey(event.keyCode)
- 0x3537 -> getInputRedmagicButtonKey(event.keyCode)
- 0x358A -> getInputBackboneLabsButtonKey(event.keyCode)
- else -> getInputGenericButtonKey(event.keyCode)
- }
-
val action = when (event.action) {
- KeyEvent.ACTION_DOWN -> NativeLibrary.ButtonState.PRESSED
- KeyEvent.ACTION_UP -> NativeLibrary.ButtonState.RELEASED
+ KeyEvent.ACTION_DOWN -> NativeInput.ButtonState.PRESSED
+ KeyEvent.ACTION_UP -> NativeInput.ButtonState.RELEASED
else -> return false
}
- // Ignore invalid buttons
- if (button < 0) {
- return false
+ var controllerData = androidControllers[event.device.controllerNumber]
+ if (controllerData == null) {
+ updateControllerData()
+ controllerData = androidControllers[event.device.controllerNumber] ?: return false
}
- return NativeLibrary.onGamePadButtonEvent(
- getPlayerNumber(event.device.controllerNumber, event.deviceId),
- button,
+ NativeInput.onGamePadButtonEvent(
+ controllerData.getGUID(),
+ controllerData.getPort(),
+ event.keyCode,
action
)
+ return true
}
fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
- val device = event.device
- // Check every axis input available on the controller
- for (range in device.motionRanges) {
- val axis = range.axis
- when (device.vendorId) {
- 0x045E -> setGenericAxisInput(event, axis)
- 0x054C -> setGenericAxisInput(event, axis)
- 0x057E -> setJoyconAxisInput(event, axis)
- 0x1532 -> setRazerAxisInput(event, axis)
- else -> setGenericAxisInput(event, axis)
- }
+ val controllerData =
+ androidControllers[event.device.controllerNumber] ?: return false
+ event.device.motionRanges.forEach {
+ NativeInput.onGamePadAxisEvent(
+ controllerData.getGUID(),
+ controllerData.getPort(),
+ it.axis,
+ event.getAxisValue(it.axis)
+ )
}
-
return true
}
- private fun getPlayerNumber(index: Int, deviceId: Int = -1): Int {
- var deviceIndex = index
- if (deviceId != -1) {
- deviceIndex = controllerIds[deviceId] ?: 0
- }
-
- // TODO: Joycons are handled as different controllers. Find a way to merge them.
- return when (deviceIndex) {
- 2 -> NativeLibrary.Player2Device
- 3 -> NativeLibrary.Player3Device
- 4 -> NativeLibrary.Player4Device
- 5 -> NativeLibrary.Player5Device
- 6 -> NativeLibrary.Player6Device
- 7 -> NativeLibrary.Player7Device
- 8 -> NativeLibrary.Player8Device
- else -> if (NativeLibrary.isHandheldOnly()) {
- NativeLibrary.ConsoleDevice
- } else {
- NativeLibrary.Player1Device
- }
- }
- }
-
- private fun setStickState(playerNumber: Int, index: Int, xAxis: Float, yAxis: Float) {
- // Calculate vector size
- val r2 = xAxis * xAxis + yAxis * yAxis
- var r = sqrt(r2.toDouble()).toFloat()
-
- // Adjust range of joystick
- val deadzone = 0.15f
- var x = xAxis
- var y = yAxis
-
- if (r > deadzone) {
- val deadzoneFactor = 1.0f / r * (r - deadzone) / (1.0f - deadzone)
- x *= deadzoneFactor
- y *= deadzoneFactor
- r *= deadzoneFactor
- } else {
- x = 0.0f
- y = 0.0f
- }
-
- // Normalize joystick
- if (r > 1.0f) {
- x /= r
- y /= r
- }
-
- NativeLibrary.onGamePadJoystickEvent(
- playerNumber,
- index,
- x,
- -y
- )
- }
-
- private fun getAxisToButton(axis: Float): Int {
- return if (axis > 0.5f) {
- NativeLibrary.ButtonState.PRESSED
- } else {
- NativeLibrary.ButtonState.RELEASED
- }
- }
-
- private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) {
- NativeLibrary.onGamePadButtonEvent(
- playerNumber,
- NativeLibrary.ButtonType.DPAD_UP,
- getAxisToButton(-yAxis)
- )
- NativeLibrary.onGamePadButtonEvent(
- playerNumber,
- NativeLibrary.ButtonType.DPAD_DOWN,
- getAxisToButton(yAxis)
- )
- NativeLibrary.onGamePadButtonEvent(
- playerNumber,
- NativeLibrary.ButtonType.DPAD_LEFT,
- getAxisToButton(-xAxis)
- )
- NativeLibrary.onGamePadButtonEvent(
- playerNumber,
- NativeLibrary.ButtonType.DPAD_RIGHT,
- getAxisToButton(xAxis)
- )
- }
-
- private fun getInputDS5ButtonKey(key: Int): Int {
- // The missing ds5 buttons are axis
- return when (key) {
- KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
- KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
- KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
- KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
- KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
- KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
- KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
- KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
- KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
- KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
- else -> -1
- }
- }
-
- private fun getInputJoyconButtonKey(key: Int): Int {
- // Joycon support is half dead. A lot of buttons can't be mapped
- return when (key) {
- KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
- KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
- KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X
- KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y
- KeyEvent.KEYCODE_DPAD_UP -> NativeLibrary.ButtonType.DPAD_UP
- KeyEvent.KEYCODE_DPAD_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
- KeyEvent.KEYCODE_DPAD_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
- KeyEvent.KEYCODE_DPAD_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
- KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
- KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
- KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
- KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
- KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
- KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
- KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
- KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
- else -> -1
- }
- }
-
- private fun getInputXboxButtonKey(key: Int): Int {
- // The missing xbox buttons are axis
- return when (key) {
- KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A
- KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_B
- KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X
- KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y
- KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
- KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
- KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
- KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
- KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
- KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
- else -> -1
- }
- }
-
- private fun getInputRazerButtonKey(key: Int): Int {
- // The missing xbox buttons are axis
- return when (key) {
- KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
- KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
- KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
- KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
- KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
- KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
- KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
- KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
- KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
- KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
- else -> -1
- }
- }
-
- private fun getInputRedmagicButtonKey(key: Int): Int {
- return when (key) {
- KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
- KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
- KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
- KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
- KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
- KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
- KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
- KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
- KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
- KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
- KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
- KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
- else -> -1
- }
- }
-
- private fun getInputBackboneLabsButtonKey(key: Int): Int {
- return when (key) {
- KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
- KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
- KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
- KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
- KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
- KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
- KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
- KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
- KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
- KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
- KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
- KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
- else -> -1
- }
- }
-
- private fun getInputGenericButtonKey(key: Int): Int {
- return when (key) {
- KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A
- KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_B
- KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X
- KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y
- KeyEvent.KEYCODE_DPAD_UP -> NativeLibrary.ButtonType.DPAD_UP
- KeyEvent.KEYCODE_DPAD_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
- KeyEvent.KEYCODE_DPAD_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
- KeyEvent.KEYCODE_DPAD_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
- KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
- KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
- KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
- KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
- KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
- KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
- KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
- KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
- else -> -1
- }
- }
-
- private fun setGenericAxisInput(event: MotionEvent, axis: Int) {
- val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
-
- when (axis) {
- MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
- setStickState(
- playerNumber,
- NativeLibrary.StickType.STICK_L,
- event.getAxisValue(MotionEvent.AXIS_X),
- event.getAxisValue(MotionEvent.AXIS_Y)
- )
- MotionEvent.AXIS_RX, MotionEvent.AXIS_RY ->
- setStickState(
- playerNumber,
- NativeLibrary.StickType.STICK_R,
- event.getAxisValue(MotionEvent.AXIS_RX),
- event.getAxisValue(MotionEvent.AXIS_RY)
- )
- MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ ->
- setStickState(
- playerNumber,
- NativeLibrary.StickType.STICK_R,
- event.getAxisValue(MotionEvent.AXIS_Z),
- event.getAxisValue(MotionEvent.AXIS_RZ)
- )
- MotionEvent.AXIS_LTRIGGER ->
- NativeLibrary.onGamePadButtonEvent(
- playerNumber,
- NativeLibrary.ButtonType.TRIGGER_ZL,
- getAxisToButton(event.getAxisValue(MotionEvent.AXIS_LTRIGGER))
- )
- MotionEvent.AXIS_BRAKE ->
- NativeLibrary.onGamePadButtonEvent(
- playerNumber,
- NativeLibrary.ButtonType.TRIGGER_ZL,
- getAxisToButton(event.getAxisValue(MotionEvent.AXIS_BRAKE))
- )
- MotionEvent.AXIS_RTRIGGER ->
- NativeLibrary.onGamePadButtonEvent(
- playerNumber,
- NativeLibrary.ButtonType.TRIGGER_ZR,
- getAxisToButton(event.getAxisValue(MotionEvent.AXIS_RTRIGGER))
- )
- MotionEvent.AXIS_GAS ->
- NativeLibrary.onGamePadButtonEvent(
- playerNumber,
- NativeLibrary.ButtonType.TRIGGER_ZR,
- getAxisToButton(event.getAxisValue(MotionEvent.AXIS_GAS))
- )
- MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y ->
- setAxisDpadState(
- playerNumber,
- event.getAxisValue(MotionEvent.AXIS_HAT_X),
- event.getAxisValue(MotionEvent.AXIS_HAT_Y)
- )
- }
- }
-
- private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
- // Joycon support is half dead. Right joystick doesn't work
- val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
-
- when (axis) {
- MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
- setStickState(
- playerNumber,
- NativeLibrary.StickType.STICK_L,
- event.getAxisValue(MotionEvent.AXIS_X),
- event.getAxisValue(MotionEvent.AXIS_Y)
- )
- MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ ->
- setStickState(
- playerNumber,
- NativeLibrary.StickType.STICK_R,
- event.getAxisValue(MotionEvent.AXIS_Z),
- event.getAxisValue(MotionEvent.AXIS_RZ)
- )
- MotionEvent.AXIS_RX, MotionEvent.AXIS_RY ->
- setStickState(
- playerNumber,
- NativeLibrary.StickType.STICK_R,
- event.getAxisValue(MotionEvent.AXIS_RX),
- event.getAxisValue(MotionEvent.AXIS_RY)
- )
- }
- }
-
- private fun setRazerAxisInput(event: MotionEvent, axis: Int) {
- val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
-
- when (axis) {
- MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
- setStickState(
- playerNumber,
- NativeLibrary.StickType.STICK_L,
- event.getAxisValue(MotionEvent.AXIS_X),
- event.getAxisValue(MotionEvent.AXIS_Y)
- )
- MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ ->
- setStickState(
- playerNumber,
- NativeLibrary.StickType.STICK_R,
- event.getAxisValue(MotionEvent.AXIS_Z),
- event.getAxisValue(MotionEvent.AXIS_RZ)
- )
- MotionEvent.AXIS_BRAKE ->
- NativeLibrary.onGamePadButtonEvent(
- playerNumber,
- NativeLibrary.ButtonType.TRIGGER_ZL,
- getAxisToButton(event.getAxisValue(MotionEvent.AXIS_BRAKE))
- )
- MotionEvent.AXIS_GAS ->
- NativeLibrary.onGamePadButtonEvent(
- playerNumber,
- NativeLibrary.ButtonType.TRIGGER_ZR,
- getAxisToButton(event.getAxisValue(MotionEvent.AXIS_GAS))
- )
- MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y ->
- setAxisDpadState(
- playerNumber,
- event.getAxisValue(MotionEvent.AXIS_HAT_X),
- event.getAxisValue(MotionEvent.AXIS_HAT_Y)
- )
- }
- }
-
- fun getGameControllerIds(): Map<Int, Int> {
- val gameControllerDeviceIds = mutableMapOf<Int, Int>()
+ fun getDevices(): Map<Int, YuzuPhysicalDevice> {
+ val gameControllerDeviceIds = mutableMapOf<Int, YuzuPhysicalDevice>()
val deviceIds = InputDevice.getDeviceIds()
- var controllerSlot = 1
+ var port = 0
+ val inputSettings = NativeConfig.getInputSettings(true)
deviceIds.forEach { deviceId ->
InputDevice.getDevice(deviceId)?.apply {
- // Don't over-assign controllers
- if (controllerSlot >= 8) {
- return gameControllerDeviceIds
- }
-
// Verify that the device has gamepad buttons, control sticks, or both.
if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD ||
sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
) {
- // This device is a game controller. Store its device ID.
- if (deviceId and id and vendorId and productId != 0) {
- // Additionally filter out devices that have no ID
- gameControllerDeviceIds
- .takeIf { !it.contains(deviceId) }
- ?.put(deviceId, controllerSlot)
- controllerSlot++
+ if (!gameControllerDeviceIds.contains(controllerNumber)) {
+ gameControllerDeviceIds[controllerNumber] = YuzuPhysicalDevice(
+ this,
+ port,
+ inputSettings[port].useSystemVibrator
+ )
}
+ port++
}
}
}
return gameControllerDeviceIds
}
+
+ fun updateControllerData() {
+ androidControllers = getDevices()
+ androidControllers.forEach {
+ NativeInput.registerController(it.value)
+ }
+
+ // Register the input overlay on a dedicated port for all player 1 vibrations
+ NativeInput.registerController(YuzuInputOverlayDevice(androidControllers.isEmpty(), 100))
+ registeredControllers.clear()
+ NativeInput.getInputDevices().forEach {
+ registeredControllers.add(ParamPackage(it))
+ }
+ registeredControllers.sortBy { it.get("port", 0) }
+ }
+
+ fun InputDevice.getGUID(): String = String.format("%016x%016x", productId, vendorId)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LifecycleUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LifecycleUtils.kt
new file mode 100644
index 000000000..d5c19c681
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LifecycleUtils.kt
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * Collects this [Flow] with a given [LifecycleOwner].
+ * @param scope [LifecycleOwner] that this [Flow] will be collected with.
+ * @param repeatState When to repeat collection on this [Flow].
+ * @param resetState Optional lambda to reset state of an underlying [MutableStateFlow] after
+ * [stateCollector] has been run.
+ * @param stateCollector Lambda that receives new state.
+ */
+inline fun <reified T> Flow<T>.collect(
+ scope: LifecycleOwner,
+ repeatState: Lifecycle.State = Lifecycle.State.CREATED,
+ crossinline resetState: () -> Unit = {},
+ crossinline stateCollector: (state: T) -> Unit
+) {
+ scope.apply {
+ lifecycleScope.launch {
+ repeatOnLifecycle(repeatState) {
+ this@collect.collect {
+ stateCollector(it)
+ resetState()
+ }
+ }
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
index a4c14b3a7..7228f25d2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
@@ -6,6 +6,8 @@ package org.yuzu.yuzu_emu.utils
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
+import org.yuzu.yuzu_emu.features.input.model.PlayerInput
+
object NativeConfig {
/**
* Loads global config.
@@ -168,4 +170,17 @@ object NativeConfig {
*/
@Synchronized
external fun setOverlayControlData(overlayControlData: Array<OverlayControlData>)
+
+ @Synchronized
+ external fun getInputSettings(global: Boolean): Array<PlayerInput>
+
+ @Synchronized
+ external fun setInputSettings(value: Array<PlayerInput>, global: Boolean)
+
+ /**
+ * Saves control values for a specific player
+ * Must be used when per game config is loaded
+ */
+ @Synchronized
+ external fun saveControlPlayerValues()
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt
index 68ed66565..331b7ddca 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt
@@ -14,7 +14,7 @@ import android.os.Build
import android.os.Handler
import android.os.Looper
import java.io.IOException
-import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.features.input.NativeInput
class NfcReader(private val activity: Activity) {
private var nfcAdapter: NfcAdapter? = null
@@ -76,12 +76,12 @@ class NfcReader(private val activity: Activity) {
amiibo.connect()
val tagData = ntag215ReadAll(amiibo) ?: return
- NativeLibrary.onReadNfcTag(tagData)
+ NativeInput.onReadNfcTag(tagData)
nfcAdapter?.ignore(
tag,
1000,
- { NativeLibrary.onRemoveNfcTag() },
+ { NativeInput.onRemoveNfcTag() },
Handler(Looper.getMainLooper())
)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ParamPackage.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ParamPackage.kt
new file mode 100644
index 000000000..83fc7da3c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ParamPackage.kt
@@ -0,0 +1,141 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+// Kotlin version of src/common/param_package.h
+class ParamPackage(serialized: String = "") {
+ private val KEY_VALUE_SEPARATOR = ":"
+ private val PARAM_SEPARATOR = ","
+
+ private val ESCAPE_CHARACTER = "$"
+ private val KEY_VALUE_SEPARATOR_ESCAPE = "$0"
+ private val PARAM_SEPARATOR_ESCAPE = "$1"
+ private val ESCAPE_CHARACTER_ESCAPE = "$2"
+
+ private val EMPTY_PLACEHOLDER = "[empty]"
+
+ val data = mutableMapOf<String, String>()
+
+ init {
+ val pairs = serialized.split(PARAM_SEPARATOR)
+ for (pair in pairs) {
+ val keyValue = pair.split(KEY_VALUE_SEPARATOR).toMutableList()
+ if (keyValue.size != 2) {
+ Log.error("[ParamPackage] Invalid key pair $keyValue")
+ continue
+ }
+
+ keyValue.forEachIndexed { i: Int, _: String ->
+ keyValue[i] = keyValue[i].replace(KEY_VALUE_SEPARATOR_ESCAPE, KEY_VALUE_SEPARATOR)
+ keyValue[i] = keyValue[i].replace(PARAM_SEPARATOR_ESCAPE, PARAM_SEPARATOR)
+ keyValue[i] = keyValue[i].replace(ESCAPE_CHARACTER_ESCAPE, ESCAPE_CHARACTER)
+ }
+
+ set(keyValue[0], keyValue[1])
+ }
+ }
+
+ constructor(params: List<Pair<String, String>>) : this() {
+ params.forEach {
+ data[it.first] = it.second
+ }
+ }
+
+ fun serialize(): String {
+ if (data.isEmpty()) {
+ return EMPTY_PLACEHOLDER
+ }
+
+ val result = StringBuilder()
+ data.forEach {
+ val keyValue = mutableListOf(it.key, it.value)
+ keyValue.forEachIndexed { i, _ ->
+ keyValue[i] = keyValue[i].replace(ESCAPE_CHARACTER, ESCAPE_CHARACTER_ESCAPE)
+ keyValue[i] = keyValue[i].replace(PARAM_SEPARATOR, PARAM_SEPARATOR_ESCAPE)
+ keyValue[i] = keyValue[i].replace(KEY_VALUE_SEPARATOR, KEY_VALUE_SEPARATOR_ESCAPE)
+ }
+ result.append("${keyValue[0]}$KEY_VALUE_SEPARATOR${keyValue[1]}$PARAM_SEPARATOR")
+ }
+ return result.removeSuffix(PARAM_SEPARATOR).toString()
+ }
+
+ fun get(key: String, defaultValue: String): String =
+ if (has(key)) {
+ data[key]!!
+ } else {
+ Log.debug("[ParamPackage] key $key not found")
+ defaultValue
+ }
+
+ fun get(key: String, defaultValue: Int): Int =
+ if (has(key)) {
+ try {
+ data[key]!!.toInt()
+ } catch (e: NumberFormatException) {
+ Log.debug("[ParamPackage] failed to convert ${data[key]!!} to int")
+ defaultValue
+ }
+ } else {
+ Log.debug("[ParamPackage] key $key not found")
+ defaultValue
+ }
+
+ private fun Int.toBoolean(): Boolean =
+ if (this == 1) {
+ true
+ } else if (this == 0) {
+ false
+ } else {
+ throw Exception("Tried to convert a value to a boolean that was not 0 or 1!")
+ }
+
+ fun get(key: String, defaultValue: Boolean): Boolean =
+ if (has(key)) {
+ try {
+ get(key, if (defaultValue) 1 else 0).toBoolean()
+ } catch (e: Exception) {
+ Log.debug("[ParamPackage] failed to convert ${data[key]!!} to boolean")
+ defaultValue
+ }
+ } else {
+ Log.debug("[ParamPackage] key $key not found")
+ defaultValue
+ }
+
+ fun get(key: String, defaultValue: Float): Float =
+ if (has(key)) {
+ try {
+ data[key]!!.toFloat()
+ } catch (e: NumberFormatException) {
+ Log.debug("[ParamPackage] failed to convert ${data[key]!!} to float")
+ defaultValue
+ }
+ } else {
+ Log.debug("[ParamPackage] key $key not found")
+ defaultValue
+ }
+
+ fun set(key: String, value: String) {
+ data[key] = value
+ }
+
+ fun set(key: String, value: Int) {
+ data[key] = value.toString()
+ }
+
+ fun Boolean.toInt(): Int = if (this) 1 else 0
+ fun set(key: String, value: Boolean) {
+ data[key] = value.toInt().toString()
+ }
+
+ fun set(key: String, value: Float) {
+ data[key] = value.toString()
+ }
+
+ fun has(key: String): Boolean = data.containsKey(key)
+
+ fun erase(key: String) = data.remove(key)
+
+ fun clear() = data.clear()
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt
index ffbfa9337..244091aec 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt
@@ -3,8 +3,10 @@
package org.yuzu.yuzu_emu.utils
+import android.text.TextUtils
import android.view.View
import android.view.ViewGroup
+import android.widget.TextView
object ViewUtils {
fun showView(view: View, length: Long = 300) {
@@ -57,4 +59,35 @@ object ViewUtils {
}
this.layoutParams = layoutParams
}
+
+ /**
+ * Shows or hides a view.
+ * @param visible Whether a view will be made View.VISIBLE or View.INVISIBLE/GONE.
+ * @param gone Optional parameter for hiding a view. Uses View.GONE if true and View.INVISIBLE otherwise.
+ */
+ fun View.setVisible(visible: Boolean, gone: Boolean = true) {
+ visibility = if (visible) {
+ View.VISIBLE
+ } else {
+ if (gone) {
+ View.GONE
+ } else {
+ View.INVISIBLE
+ }
+ }
+ }
+
+ /**
+ * Starts a marquee on some text.
+ * @param delay Optional parameter for changing the start delay. 3 seconds of delay by default.
+ */
+ fun TextView.marquee(delay: Long = 3000) {
+ ellipsize = null
+ marqueeRepeatLimit = -1
+ isSingleLine = true
+ postDelayed({
+ ellipsize = TextUtils.TruncateAt.MARQUEE
+ isSelected = true
+ }, delay)
+ }
}
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt
index 20b319c12..ec8ae5c57 100644
--- a/src/android/app/src/main/jni/CMakeLists.txt
+++ b/src/android/app/src/main/jni/CMakeLists.txt
@@ -12,6 +12,7 @@ add_library(yuzu-android SHARED
native_log.cpp
android_config.cpp
android_config.h
+ native_input.cpp
)
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp
index e147560c3..a79a64afb 100644
--- a/src/android/app/src/main/jni/android_config.cpp
+++ b/src/android/app/src/main/jni/android_config.cpp
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <common/logging/log.h>
+#include <input_common/main.h>
#include "android_config.h"
#include "android_settings.h"
#include "common/settings_setting.h"
@@ -32,6 +34,7 @@ void AndroidConfig::ReadAndroidValues() {
ReadOverlayValues();
}
ReadDriverValues();
+ ReadAndroidControlValues();
}
void AndroidConfig::ReadAndroidUIValues() {
@@ -107,6 +110,76 @@ void AndroidConfig::ReadOverlayValues() {
EndGroup();
}
+void AndroidConfig::ReadAndroidPlayerValues(std::size_t player_index) {
+ std::string player_prefix;
+ if (type != ConfigType::InputProfile) {
+ player_prefix.append("player_").append(ToString(player_index)).append("_");
+ }
+
+ auto& player = Settings::values.players.GetValue()[player_index];
+ if (IsCustomConfig()) {
+ const auto profile_name =
+ ReadStringSetting(std::string(player_prefix).append("profile_name"));
+ if (profile_name.empty()) {
+ // Use the global input config
+ player = Settings::values.players.GetValue(true)[player_index];
+ player.profile_name = "";
+ return;
+ }
+ }
+
+ // Android doesn't have default options for controllers. We have the input overlay for that.
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ const std::string default_param;
+ auto& player_buttons = player.buttons[i];
+
+ player_buttons = ReadStringSetting(
+ std::string(player_prefix).append(Settings::NativeButton::mapping[i]), default_param);
+ if (player_buttons.empty()) {
+ player_buttons = default_param;
+ }
+ }
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ const std::string default_param;
+ auto& player_analogs = player.analogs[i];
+
+ player_analogs = ReadStringSetting(
+ std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), default_param);
+ if (player_analogs.empty()) {
+ player_analogs = default_param;
+ }
+ }
+ for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+ const std::string default_param;
+ auto& player_motions = player.motions[i];
+
+ player_motions = ReadStringSetting(
+ std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), default_param);
+ if (player_motions.empty()) {
+ player_motions = default_param;
+ }
+ }
+ player.use_system_vibrator = ReadBooleanSetting(
+ std::string(player_prefix).append("use_system_vibrator"), player_index == 0);
+}
+
+void AndroidConfig::ReadAndroidControlValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+ Settings::values.players.SetGlobal(!IsCustomConfig());
+ for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+ ReadAndroidPlayerValues(p);
+ }
+ if (IsCustomConfig()) {
+ EndGroup();
+ return;
+ }
+ // ReadDebugControlValues();
+ // ReadHidbusValues();
+
+ EndGroup();
+}
+
void AndroidConfig::SaveAndroidValues() {
if (global) {
SaveAndroidUIValues();
@@ -114,6 +187,7 @@ void AndroidConfig::SaveAndroidValues() {
SaveOverlayValues();
}
SaveDriverValues();
+ SaveAndroidControlValues();
WriteToIni();
}
@@ -187,6 +261,52 @@ void AndroidConfig::SaveOverlayValues() {
EndGroup();
}
+void AndroidConfig::SaveAndroidPlayerValues(std::size_t player_index) {
+ std::string player_prefix;
+ if (type != ConfigType::InputProfile) {
+ player_prefix = std::string("player_").append(ToString(player_index)).append("_");
+ }
+
+ const auto& player = Settings::values.players.GetValue()[player_index];
+ if (IsCustomConfig() && player.profile_name.empty()) {
+ // No custom profile selected
+ return;
+ }
+
+ const std::string default_param;
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]),
+ player.buttons[i], std::make_optional(default_param));
+ }
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ WriteStringSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]),
+ player.analogs[i], std::make_optional(default_param));
+ }
+ for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+ WriteStringSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]),
+ player.motions[i], std::make_optional(default_param));
+ }
+ WriteBooleanSetting(std::string(player_prefix).append("use_system_vibrator"),
+ player.use_system_vibrator, std::make_optional(player_index == 0));
+}
+
+void AndroidConfig::SaveAndroidControlValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+ Settings::values.players.SetGlobal(!IsCustomConfig());
+ for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+ SaveAndroidPlayerValues(p);
+ }
+ if (IsCustomConfig()) {
+ EndGroup();
+ return;
+ }
+ // SaveDebugControlValues();
+ // SaveHidbusValues();
+
+ EndGroup();
+}
+
std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
auto& map = Settings::values.linkage.by_category;
if (map.contains(category)) {
@@ -194,3 +314,24 @@ std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::
}
return AndroidSettings::values.linkage.by_category[category];
}
+
+void AndroidConfig::ReadAndroidControlPlayerValues(std::size_t player_index) {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+ ReadPlayerValues(player_index);
+ ReadAndroidPlayerValues(player_index);
+
+ EndGroup();
+}
+
+void AndroidConfig::SaveAndroidControlPlayerValues(std::size_t player_index) {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+ LOG_DEBUG(Config, "Saving players control configuration values");
+ SavePlayerValues(player_index);
+ SaveAndroidPlayerValues(player_index);
+
+ EndGroup();
+
+ WriteToIni();
+}
diff --git a/src/android/app/src/main/jni/android_config.h b/src/android/app/src/main/jni/android_config.h
index 693e1e3f0..28ef5d0a8 100644
--- a/src/android/app/src/main/jni/android_config.h
+++ b/src/android/app/src/main/jni/android_config.h
@@ -13,7 +13,12 @@ public:
void ReloadAllValues() override;
void SaveAllValues() override;
+ void ReadAndroidControlPlayerValues(std::size_t player_index);
+ void SaveAndroidControlPlayerValues(std::size_t player_index);
+
protected:
+ void ReadAndroidPlayerValues(std::size_t player_index);
+ void ReadAndroidControlValues();
void ReadAndroidValues();
void ReadAndroidUIValues();
void ReadDriverValues();
@@ -27,6 +32,8 @@ protected:
void ReadUILayoutValues() override {}
void ReadMultiplayerValues() override {}
+ void SaveAndroidPlayerValues(std::size_t player_index);
+ void SaveAndroidControlValues();
void SaveAndroidValues();
void SaveAndroidUIValues();
void SaveDriverValues();
diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp
index c927cddda..06db55369 100644
--- a/src/android/app/src/main/jni/emu_window/emu_window.cpp
+++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp
@@ -5,6 +5,7 @@
#include "common/android/id_cache.h"
#include "common/logging/log.h"
+#include "input_common/drivers/android.h"
#include "input_common/drivers/touch_screen.h"
#include "input_common/drivers/virtual_amiibo.h"
#include "input_common/drivers/virtual_gamepad.h"
@@ -24,39 +25,18 @@ void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
void EmuWindow_Android::OnTouchPressed(int id, float x, float y) {
const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
- m_input_subsystem->GetTouchScreen()->TouchPressed(touch_x, touch_y, id);
+ EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchPressed(touch_x,
+ touch_y, id);
}
void EmuWindow_Android::OnTouchMoved(int id, float x, float y) {
const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
- m_input_subsystem->GetTouchScreen()->TouchMoved(touch_x, touch_y, id);
+ EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchMoved(touch_x,
+ touch_y, id);
}
void EmuWindow_Android::OnTouchReleased(int id) {
- m_input_subsystem->GetTouchScreen()->TouchReleased(id);
-}
-
-void EmuWindow_Android::OnGamepadButtonEvent(int player_index, int button_id, bool pressed) {
- m_input_subsystem->GetVirtualGamepad()->SetButtonState(player_index, button_id, pressed);
-}
-
-void EmuWindow_Android::OnGamepadJoystickEvent(int player_index, int stick_id, float x, float y) {
- m_input_subsystem->GetVirtualGamepad()->SetStickPosition(player_index, stick_id, x, y);
-}
-
-void EmuWindow_Android::OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x,
- float gyro_y, float gyro_z, float accel_x,
- float accel_y, float accel_z) {
- m_input_subsystem->GetVirtualGamepad()->SetMotionState(
- player_index, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z);
-}
-
-void EmuWindow_Android::OnReadNfcTag(std::span<u8> data) {
- m_input_subsystem->GetVirtualAmiibo()->LoadAmiibo(data);
-}
-
-void EmuWindow_Android::OnRemoveNfcTag() {
- m_input_subsystem->GetVirtualAmiibo()->CloseAmiibo();
+ EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchReleased(id);
}
void EmuWindow_Android::OnFrameDisplayed() {
@@ -67,10 +47,9 @@ void EmuWindow_Android::OnFrameDisplayed() {
}
}
-EmuWindow_Android::EmuWindow_Android(InputCommon::InputSubsystem* input_subsystem,
- ANativeWindow* surface,
+EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface,
std::shared_ptr<Common::DynamicLibrary> driver_library)
- : m_input_subsystem{input_subsystem}, m_driver_library{driver_library} {
+ : m_driver_library{driver_library} {
LOG_INFO(Frontend, "initializing");
if (!surface) {
@@ -80,10 +59,4 @@ EmuWindow_Android::EmuWindow_Android(InputCommon::InputSubsystem* input_subsyste
OnSurfaceChanged(surface);
window_info.type = Core::Frontend::WindowSystemType::Android;
-
- m_input_subsystem->Initialize();
-}
-
-EmuWindow_Android::~EmuWindow_Android() {
- m_input_subsystem->Shutdown();
}
diff --git a/src/android/app/src/main/jni/emu_window/emu_window.h b/src/android/app/src/main/jni/emu_window/emu_window.h
index a34a0e479..d7b5fc6da 100644
--- a/src/android/app/src/main/jni/emu_window/emu_window.h
+++ b/src/android/app/src/main/jni/emu_window/emu_window.h
@@ -30,22 +30,17 @@ private:
class EmuWindow_Android final : public Core::Frontend::EmuWindow {
public:
- EmuWindow_Android(InputCommon::InputSubsystem* input_subsystem, ANativeWindow* surface,
+ EmuWindow_Android(ANativeWindow* surface,
std::shared_ptr<Common::DynamicLibrary> driver_library);
- ~EmuWindow_Android();
+ ~EmuWindow_Android() = default;
void OnSurfaceChanged(ANativeWindow* surface);
+ void OnFrameDisplayed() override;
+
void OnTouchPressed(int id, float x, float y);
void OnTouchMoved(int id, float x, float y);
void OnTouchReleased(int id);
- void OnGamepadButtonEvent(int player_index, int button_id, bool pressed);
- void OnGamepadJoystickEvent(int player_index, int stick_id, float x, float y);
- void OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x, float gyro_y,
- float gyro_z, float accel_x, float accel_y, float accel_z);
- void OnReadNfcTag(std::span<u8> data);
- void OnRemoveNfcTag();
- void OnFrameDisplayed() override;
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override {
return {std::make_unique<GraphicsContext_Android>(m_driver_library)};
@@ -55,8 +50,6 @@ public:
};
private:
- InputCommon::InputSubsystem* m_input_subsystem{};
-
float m_window_width{};
float m_window_height{};
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index a4d8454e8..4ea82e217 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -88,6 +88,10 @@ FileSys::ManualContentProvider* EmulationSession::GetContentProvider() {
return m_manual_provider.get();
}
+InputCommon::InputSubsystem& EmulationSession::GetInputSubsystem() {
+ return m_input_subsystem;
+}
+
const EmuWindow_Android& EmulationSession::Window() const {
return *m_window;
}
@@ -198,6 +202,8 @@ void EmulationSession::InitializeSystem(bool reload) {
Common::Log::Initialize();
Common::Log::SetColorConsoleBackendEnabled(true);
Common::Log::Start();
+
+ m_input_subsystem.Initialize();
}
// Initialize filesystem.
@@ -222,8 +228,7 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
std::scoped_lock lock(m_mutex);
// Create the render window.
- m_window =
- std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, m_vulkan_library);
+ m_window = std::make_unique<EmuWindow_Android>(m_native_window, m_vulkan_library);
// Initialize system.
jauto android_keyboard = std::make_unique<Common::Android::SoftwareKeyboard::AndroidKeyboard>();
@@ -355,60 +360,6 @@ void EmulationSession::RunEmulation() {
m_applet_id = static_cast<int>(Service::AM::AppletId::Application);
}
-bool EmulationSession::IsHandheldOnly() {
- jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
-
- if (npad_style_set.fullkey == 1) {
- return false;
- }
-
- if (npad_style_set.handheld == 0) {
- return false;
- }
-
- return !Settings::IsDockedMode();
-}
-
-void EmulationSession::SetDeviceType([[maybe_unused]] int index, int type) {
- jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
- controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
-}
-
-void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) {
- jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
-
- // Ensure that player1 is configured correctly and handheld disconnected
- if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
- jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
-
- if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
- handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
- controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
- handheld->Disconnect();
- }
- }
-
- // Ensure that handheld is configured correctly and player 1 disconnected
- if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
- jauto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
-
- if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
- player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
- controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
- player1->Disconnect();
- }
- }
-
- if (!controller->IsConnected()) {
- controller->Connect();
- }
-}
-
-void EmulationSession::OnGamepadDisconnectEvent([[maybe_unused]] int index) {
- jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
- controller->Disconnect();
-}
-
Common::Android::SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() {
return m_software_keyboard;
}
@@ -453,7 +404,9 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath,
const size_t program_index,
const bool frontend_initiated) {
MicroProfileOnThreadCreate("EmuThread");
- SCOPE_EXIT({ MicroProfileShutdown(); });
+ SCOPE_EXIT {
+ MicroProfileShutdown();
+ };
LOG_INFO(Frontend, "starting");
@@ -462,7 +415,9 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath,
return Core::SystemResultStatus::ErrorLoader;
}
- SCOPE_EXIT({ EmulationSession::GetInstance().ShutdownEmulation(); });
+ SCOPE_EXIT {
+ EmulationSession::GetInstance().ShutdownEmulation();
+ };
jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath, program_index,
frontend_initiated);
@@ -574,14 +529,14 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_getSystemDriverInfo(
nullptr, nullptr, file_redirect_dir_, nullptr);
auto driver_library = std::make_shared<Common::DynamicLibrary>(handle);
InputCommon::InputSubsystem input_subsystem;
- auto m_window = std::make_unique<EmuWindow_Android>(
- &input_subsystem, ANativeWindow_fromSurface(env, j_surf), driver_library);
+ auto window =
+ std::make_unique<EmuWindow_Android>(ANativeWindow_fromSurface(env, j_surf), driver_library);
Vulkan::vk::InstanceDispatch dld;
Vulkan::vk::Instance vk_instance = Vulkan::CreateInstance(
*driver_library, dld, VK_API_VERSION_1_1, Core::Frontend::WindowSystemType::Android);
- auto surface = Vulkan::CreateSurface(vk_instance, m_window->GetWindowInfo());
+ auto surface = Vulkan::CreateSurface(vk_instance, window->GetWindowInfo());
auto device = Vulkan::CreateDevice(vk_instance, dld, *surface);
@@ -622,103 +577,6 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
}
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) {
- return EmulationSession::GetInstance().IsHandheldOnly();
-}
-
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType(JNIEnv* env, jclass clazz,
- jint j_device, jint j_type) {
- if (EmulationSession::GetInstance().IsRunning()) {
- EmulationSession::GetInstance().SetDeviceType(j_device, j_type);
- }
- return static_cast<jboolean>(true);
-}
-
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent(JNIEnv* env, jclass clazz,
- jint j_device) {
- if (EmulationSession::GetInstance().IsRunning()) {
- EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
- }
- return static_cast<jboolean>(true);
-}
-
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(JNIEnv* env, jclass clazz,
- jint j_device) {
- if (EmulationSession::GetInstance().IsRunning()) {
- EmulationSession::GetInstance().OnGamepadDisconnectEvent(j_device);
- }
- return static_cast<jboolean>(true);
-}
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent(JNIEnv* env, jclass clazz,
- jint j_device, jint j_button,
- jint action) {
- if (EmulationSession::GetInstance().IsRunning()) {
- // Ensure gamepad is connected
- EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
- EmulationSession::GetInstance().Window().OnGamepadButtonEvent(j_device, j_button,
- action != 0);
- }
- return static_cast<jboolean>(true);
-}
-
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent(JNIEnv* env, jclass clazz,
- jint j_device, jint stick_id,
- jfloat x, jfloat y) {
- if (EmulationSession::GetInstance().IsRunning()) {
- EmulationSession::GetInstance().Window().OnGamepadJoystickEvent(j_device, stick_id, x, y);
- }
- return static_cast<jboolean>(true);
-}
-
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMotionEvent(
- JNIEnv* env, jclass clazz, jint j_device, jlong delta_timestamp, jfloat gyro_x, jfloat gyro_y,
- jfloat gyro_z, jfloat accel_x, jfloat accel_y, jfloat accel_z) {
- if (EmulationSession::GetInstance().IsRunning()) {
- EmulationSession::GetInstance().Window().OnGamepadMotionEvent(
- j_device, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z);
- }
- return static_cast<jboolean>(true);
-}
-
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag(JNIEnv* env, jclass clazz,
- jbyteArray j_data) {
- jboolean isCopy{false};
- std::span<u8> data(reinterpret_cast<u8*>(env->GetByteArrayElements(j_data, &isCopy)),
- static_cast<size_t>(env->GetArrayLength(j_data)));
-
- if (EmulationSession::GetInstance().IsRunning()) {
- EmulationSession::GetInstance().Window().OnReadNfcTag(data);
- }
- return static_cast<jboolean>(true);
-}
-
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag(JNIEnv* env, jclass clazz) {
- if (EmulationSession::GetInstance().IsRunning()) {
- EmulationSession::GetInstance().Window().OnRemoveNfcTag();
- }
- return static_cast<jboolean>(true);
-}
-
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchPressed(JNIEnv* env, jclass clazz, jint id,
- jfloat x, jfloat y) {
- if (EmulationSession::GetInstance().IsRunning()) {
- EmulationSession::GetInstance().Window().OnTouchPressed(id, x, y);
- }
-}
-
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz, jint id,
- jfloat x, jfloat y) {
- if (EmulationSession::GetInstance().IsRunning()) {
- EmulationSession::GetInstance().Window().OnTouchMoved(id, x, y);
- }
-}
-
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass clazz, jint id) {
- if (EmulationSession::GetInstance().IsRunning()) {
- EmulationSession::GetInstance().Window().OnTouchReleased(id);
- }
-}
-
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz,
jboolean reload) {
// Initialize the emulated system.
@@ -759,6 +617,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGpuDriver(JNIEnv* env, jobject
void Java_org_yuzu_yuzu_1emu_NativeLibrary_applySettings(JNIEnv* env, jobject jobj) {
EmulationSession::GetInstance().System().ApplySettings();
+ EmulationSession::GetInstance().System().HIDCore().ReloadInputDevices();
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj) {
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h
index 47936e305..6a4551ada 100644
--- a/src/android/app/src/main/jni/native.h
+++ b/src/android/app/src/main/jni/native.h
@@ -23,6 +23,7 @@ public:
const Core::System& System() const;
Core::System& System();
FileSys::ManualContentProvider* GetContentProvider();
+ InputCommon::InputSubsystem& GetInputSubsystem();
const EmuWindow_Android& Window() const;
EmuWindow_Android& Window();
@@ -50,10 +51,6 @@ public:
const std::size_t program_index,
const bool frontend_initiated);
- bool IsHandheldOnly();
- void SetDeviceType([[maybe_unused]] int index, int type);
- void OnGamepadConnectEvent([[maybe_unused]] int index);
- void OnGamepadDisconnectEvent([[maybe_unused]] int index);
Common::Android::SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard();
static void OnEmulationStarted();
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
index 8ae10fbc7..0b26280c6 100644
--- a/src/android/app/src/main/jni/native_config.cpp
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -3,7 +3,6 @@
#include <string>
-#include <common/fs/fs_util.h>
#include <jni.h>
#include "android_config.h"
@@ -425,4 +424,120 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setOverlayControlData(
}
}
+jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInputSettings(JNIEnv* env, jobject obj,
+ jboolean j_global) {
+ Settings::values.players.SetGlobal(static_cast<bool>(j_global));
+ auto& players = Settings::values.players.GetValue();
+ jobjectArray j_input_settings =
+ env->NewObjectArray(players.size(), Common::Android::GetPlayerInputClass(), nullptr);
+ for (size_t i = 0; i < players.size(); ++i) {
+ auto j_connected = static_cast<jboolean>(players[i].connected);
+
+ jobjectArray j_buttons = env->NewObjectArray(
+ players[i].buttons.size(), Common::Android::GetStringClass(), env->NewStringUTF(""));
+ for (size_t j = 0; j < players[i].buttons.size(); ++j) {
+ env->SetObjectArrayElement(j_buttons, j,
+ Common::Android::ToJString(env, players[i].buttons[j]));
+ }
+ jobjectArray j_analogs = env->NewObjectArray(
+ players[i].analogs.size(), Common::Android::GetStringClass(), env->NewStringUTF(""));
+ for (size_t j = 0; j < players[i].analogs.size(); ++j) {
+ env->SetObjectArrayElement(j_analogs, j,
+ Common::Android::ToJString(env, players[i].analogs[j]));
+ }
+ jobjectArray j_motions = env->NewObjectArray(
+ players[i].motions.size(), Common::Android::GetStringClass(), env->NewStringUTF(""));
+ for (size_t j = 0; j < players[i].motions.size(); ++j) {
+ env->SetObjectArrayElement(j_motions, j,
+ Common::Android::ToJString(env, players[i].motions[j]));
+ }
+
+ auto j_vibration_enabled = static_cast<jboolean>(players[i].vibration_enabled);
+ auto j_vibration_strength = static_cast<jint>(players[i].vibration_strength);
+
+ auto j_body_color_left = static_cast<jlong>(players[i].body_color_left);
+ auto j_body_color_right = static_cast<jlong>(players[i].body_color_right);
+ auto j_button_color_left = static_cast<jlong>(players[i].button_color_left);
+ auto j_button_color_right = static_cast<jlong>(players[i].button_color_right);
+
+ auto j_profile_name = Common::Android::ToJString(env, players[i].profile_name);
+
+ auto j_use_system_vibrator = players[i].use_system_vibrator;
+
+ jobject playerInput = env->NewObject(
+ Common::Android::GetPlayerInputClass(), Common::Android::GetPlayerInputConstructor(),
+ j_connected, j_buttons, j_analogs, j_motions, j_vibration_enabled, j_vibration_strength,
+ j_body_color_left, j_body_color_right, j_button_color_left, j_button_color_right,
+ j_profile_name, j_use_system_vibrator);
+ env->SetObjectArrayElement(j_input_settings, i, playerInput);
+ }
+ return j_input_settings;
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setInputSettings(JNIEnv* env, jobject obj,
+ jobjectArray j_value,
+ jboolean j_global) {
+ auto& players = Settings::values.players.GetValue(static_cast<bool>(j_global));
+ int playersSize = env->GetArrayLength(j_value);
+ for (int i = 0; i < playersSize; ++i) {
+ jobject jplayer = env->GetObjectArrayElement(j_value, i);
+
+ players[i].connected = static_cast<bool>(
+ env->GetBooleanField(jplayer, Common::Android::GetPlayerInputConnectedField()));
+
+ auto j_buttons_array = static_cast<jobjectArray>(
+ env->GetObjectField(jplayer, Common::Android::GetPlayerInputButtonsField()));
+ int buttons_size = env->GetArrayLength(j_buttons_array);
+ for (int j = 0; j < buttons_size; ++j) {
+ auto button = static_cast<jstring>(env->GetObjectArrayElement(j_buttons_array, j));
+ players[i].buttons[j] = Common::Android::GetJString(env, button);
+ }
+ auto j_analogs_array = static_cast<jobjectArray>(
+ env->GetObjectField(jplayer, Common::Android::GetPlayerInputAnalogsField()));
+ int analogs_size = env->GetArrayLength(j_analogs_array);
+ for (int j = 0; j < analogs_size; ++j) {
+ auto analog = static_cast<jstring>(env->GetObjectArrayElement(j_analogs_array, j));
+ players[i].analogs[j] = Common::Android::GetJString(env, analog);
+ }
+ auto j_motions_array = static_cast<jobjectArray>(
+ env->GetObjectField(jplayer, Common::Android::GetPlayerInputMotionsField()));
+ int motions_size = env->GetArrayLength(j_motions_array);
+ for (int j = 0; j < motions_size; ++j) {
+ auto motion = static_cast<jstring>(env->GetObjectArrayElement(j_motions_array, j));
+ players[i].motions[j] = Common::Android::GetJString(env, motion);
+ }
+
+ players[i].vibration_enabled = static_cast<bool>(
+ env->GetBooleanField(jplayer, Common::Android::GetPlayerInputVibrationEnabledField()));
+ players[i].vibration_strength = static_cast<int>(
+ env->GetIntField(jplayer, Common::Android::GetPlayerInputVibrationStrengthField()));
+
+ players[i].body_color_left = static_cast<u32>(
+ env->GetLongField(jplayer, Common::Android::GetPlayerInputBodyColorLeftField()));
+ players[i].body_color_right = static_cast<u32>(
+ env->GetLongField(jplayer, Common::Android::GetPlayerInputBodyColorRightField()));
+ players[i].button_color_left = static_cast<u32>(
+ env->GetLongField(jplayer, Common::Android::GetPlayerInputButtonColorLeftField()));
+ players[i].button_color_right = static_cast<u32>(
+ env->GetLongField(jplayer, Common::Android::GetPlayerInputButtonColorRightField()));
+
+ auto profileName = static_cast<jstring>(
+ env->GetObjectField(jplayer, Common::Android::GetPlayerInputProfileNameField()));
+ players[i].profile_name = Common::Android::GetJString(env, profileName);
+
+ players[i].use_system_vibrator =
+ env->GetBooleanField(jplayer, Common::Android::GetPlayerInputUseSystemVibratorField());
+ }
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveControlPlayerValues(JNIEnv* env, jobject obj) {
+ Settings::values.players.SetGlobal(false);
+
+ // Clear all controls from the config in case the user reverted back to globals
+ per_game_config->ClearControlPlayerValues();
+ for (size_t index = 0; index < Settings::values.players.GetValue().size(); ++index) {
+ per_game_config->SaveAndroidControlPlayerValues(index);
+ }
+}
+
} // extern "C"
diff --git a/src/android/app/src/main/jni/native_input.cpp b/src/android/app/src/main/jni/native_input.cpp
new file mode 100644
index 000000000..37a65f2b8
--- /dev/null
+++ b/src/android/app/src/main/jni/native_input.cpp
@@ -0,0 +1,629 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <common/fs/fs.h>
+#include <common/fs/path_util.h>
+#include <common/settings.h>
+#include <hid_core/hid_types.h>
+#include <jni.h>
+
+#include "android_config.h"
+#include "common/android/android_common.h"
+#include "common/android/id_cache.h"
+#include "hid_core/frontend/emulated_controller.h"
+#include "hid_core/hid_core.h"
+#include "input_common/drivers/android.h"
+#include "input_common/drivers/touch_screen.h"
+#include "input_common/drivers/virtual_amiibo.h"
+#include "input_common/drivers/virtual_gamepad.h"
+#include "native.h"
+
+std::unordered_map<std::string, std::unique_ptr<AndroidConfig>> map_profiles;
+
+bool IsHandheldOnly() {
+ const auto npad_style_set =
+ EmulationSession::GetInstance().System().HIDCore().GetSupportedStyleTag();
+
+ if (npad_style_set.fullkey == 1) {
+ return false;
+ }
+
+ if (npad_style_set.handheld == 0) {
+ return false;
+ }
+
+ return !Settings::IsDockedMode();
+}
+
+std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) {
+ return filename.replace_extension();
+}
+
+bool IsProfileNameValid(std::string_view profile_name) {
+ return profile_name.find_first_of("<>:;\"/\\|,.!?*") == std::string::npos;
+}
+
+bool ProfileExistsInFilesystem(std::string_view profile_name) {
+ return Common::FS::Exists(Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "input" /
+ fmt::format("{}.ini", profile_name));
+}
+
+bool ProfileExistsInMap(const std::string& profile_name) {
+ return map_profiles.find(profile_name) != map_profiles.end();
+}
+
+bool SaveProfile(const std::string& profile_name, std::size_t player_index) {
+ if (!ProfileExistsInMap(profile_name)) {
+ return false;
+ }
+
+ Settings::values.players.GetValue()[player_index].profile_name = profile_name;
+ map_profiles[profile_name]->SaveAndroidControlPlayerValues(player_index);
+ return true;
+}
+
+bool LoadProfile(std::string& profile_name, std::size_t player_index) {
+ if (!ProfileExistsInMap(profile_name)) {
+ return false;
+ }
+
+ if (!ProfileExistsInFilesystem(profile_name)) {
+ map_profiles.erase(profile_name);
+ return false;
+ }
+
+ LOG_INFO(Config, "Loading input profile `{}`", profile_name);
+
+ Settings::values.players.GetValue()[player_index].profile_name = profile_name;
+ map_profiles[profile_name]->ReadAndroidControlPlayerValues(player_index);
+ return true;
+}
+
+void ApplyControllerConfig(size_t player_index,
+ const std::function<void(Core::HID::EmulatedController*)>& apply) {
+ auto& hid_core = EmulationSession::GetInstance().System().HIDCore();
+ if (player_index == 0) {
+ auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
+ auto* player_one = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
+ handheld->EnableConfiguration();
+ player_one->EnableConfiguration();
+ apply(handheld);
+ apply(player_one);
+ handheld->DisableConfiguration();
+ player_one->DisableConfiguration();
+ handheld->SaveCurrentConfig();
+ player_one->SaveCurrentConfig();
+ } else {
+ auto* controller = hid_core.GetEmulatedControllerByIndex(player_index);
+ controller->EnableConfiguration();
+ apply(controller);
+ controller->DisableConfiguration();
+ controller->SaveCurrentConfig();
+ }
+}
+
+void ConnectController(size_t player_index, bool connected) {
+ auto& hid_core = EmulationSession::GetInstance().System().HIDCore();
+ if (player_index == 0) {
+ auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
+ auto* player_one = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
+ handheld->EnableConfiguration();
+ player_one->EnableConfiguration();
+ if (player_one->GetNpadStyleIndex(true) == Core::HID::NpadStyleIndex::Handheld) {
+ if (connected) {
+ handheld->Connect();
+ } else {
+ handheld->Disconnect();
+ }
+ player_one->Disconnect();
+ } else {
+ if (connected) {
+ player_one->Connect();
+ } else {
+ player_one->Disconnect();
+ }
+ handheld->Disconnect();
+ }
+ handheld->DisableConfiguration();
+ player_one->DisableConfiguration();
+ handheld->SaveCurrentConfig();
+ player_one->SaveCurrentConfig();
+ } else {
+ auto* controller = hid_core.GetEmulatedControllerByIndex(player_index);
+ controller->EnableConfiguration();
+ if (connected) {
+ controller->Connect();
+ } else {
+ controller->Disconnect();
+ }
+ controller->DisableConfiguration();
+ controller->SaveCurrentConfig();
+ }
+}
+
+extern "C" {
+
+jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_isHandheldOnly(JNIEnv* env,
+ jobject j_obj) {
+ return IsHandheldOnly();
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onGamePadButtonEvent(
+ JNIEnv* env, jobject j_obj, jstring j_guid, jint j_port, jint j_button_id, jint j_action) {
+ EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->SetButtonState(
+ Common::Android::GetJString(env, j_guid), j_port, j_button_id, j_action != 0);
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onGamePadAxisEvent(
+ JNIEnv* env, jobject j_obj, jstring j_guid, jint j_port, jint j_stick_id, jfloat j_value) {
+ EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->SetAxisPosition(
+ Common::Android::GetJString(env, j_guid), j_port, j_stick_id, j_value);
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onGamePadMotionEvent(
+ JNIEnv* env, jobject j_obj, jstring j_guid, jint j_port, jlong j_delta_timestamp,
+ jfloat j_x_gyro, jfloat j_y_gyro, jfloat j_z_gyro, jfloat j_x_accel, jfloat j_y_accel,
+ jfloat j_z_accel) {
+ EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->SetMotionState(
+ Common::Android::GetJString(env, j_guid), j_port, j_delta_timestamp, j_x_gyro, j_y_gyro,
+ j_z_gyro, j_x_accel, j_y_accel, j_z_accel);
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onReadNfcTag(JNIEnv* env, jobject j_obj,
+ jbyteArray j_data) {
+ jboolean isCopy{false};
+ std::span<u8> data(reinterpret_cast<u8*>(env->GetByteArrayElements(j_data, &isCopy)),
+ static_cast<size_t>(env->GetArrayLength(j_data)));
+
+ if (EmulationSession::GetInstance().IsRunning()) {
+ EmulationSession::GetInstance().GetInputSubsystem().GetVirtualAmiibo()->LoadAmiibo(data);
+ }
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onRemoveNfcTag(JNIEnv* env, jobject j_obj) {
+ if (EmulationSession::GetInstance().IsRunning()) {
+ EmulationSession::GetInstance().GetInputSubsystem().GetVirtualAmiibo()->CloseAmiibo();
+ }
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchPressed(JNIEnv* env, jobject j_obj,
+ jint j_id, jfloat j_x_axis,
+ jfloat j_y_axis) {
+ if (EmulationSession::GetInstance().IsRunning()) {
+ EmulationSession::GetInstance().Window().OnTouchPressed(j_id, j_x_axis, j_y_axis);
+ }
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchMoved(JNIEnv* env, jobject j_obj,
+ jint j_id, jfloat j_x_axis,
+ jfloat j_y_axis) {
+ if (EmulationSession::GetInstance().IsRunning()) {
+ EmulationSession::GetInstance().Window().OnTouchMoved(j_id, j_x_axis, j_y_axis);
+ }
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchReleased(JNIEnv* env, jobject j_obj,
+ jint j_id) {
+ if (EmulationSession::GetInstance().IsRunning()) {
+ EmulationSession::GetInstance().Window().OnTouchReleased(j_id);
+ }
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onOverlayButtonEventImpl(
+ JNIEnv* env, jobject j_obj, jint j_port, jint j_button_id, jint j_action) {
+ if (EmulationSession::GetInstance().IsRunning()) {
+ EmulationSession::GetInstance().GetInputSubsystem().GetVirtualGamepad()->SetButtonState(
+ j_port, j_button_id, j_action == 1);
+ }
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onOverlayJoystickEventImpl(
+ JNIEnv* env, jobject j_obj, jint j_port, jint j_stick_id, jfloat j_x_axis, jfloat j_y_axis) {
+ if (EmulationSession::GetInstance().IsRunning()) {
+ EmulationSession::GetInstance().GetInputSubsystem().GetVirtualGamepad()->SetStickPosition(
+ j_port, j_stick_id, j_x_axis, j_y_axis);
+ }
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onDeviceMotionEvent(
+ JNIEnv* env, jobject j_obj, jint j_port, jlong j_delta_timestamp, jfloat j_x_gyro,
+ jfloat j_y_gyro, jfloat j_z_gyro, jfloat j_x_accel, jfloat j_y_accel, jfloat j_z_accel) {
+ if (EmulationSession::GetInstance().IsRunning()) {
+ EmulationSession::GetInstance().GetInputSubsystem().GetVirtualGamepad()->SetMotionState(
+ j_port, j_delta_timestamp, j_x_gyro, j_y_gyro, j_z_gyro, j_x_accel, j_y_accel,
+ j_z_accel);
+ }
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_reloadInputDevices(JNIEnv* env,
+ jobject j_obj) {
+ EmulationSession::GetInstance().System().HIDCore().ReloadInputDevices();
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_registerController(JNIEnv* env,
+ jobject j_obj,
+ jobject j_device) {
+ EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->RegisterController(j_device);
+}
+
+jobjectArray Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getInputDevices(JNIEnv* env,
+ jobject j_obj) {
+ auto devices = EmulationSession::GetInstance().GetInputSubsystem().GetInputDevices();
+ jobjectArray jdevices = env->NewObjectArray(devices.size(), Common::Android::GetStringClass(),
+ Common::Android::ToJString(env, ""));
+ for (size_t i = 0; i < devices.size(); ++i) {
+ env->SetObjectArrayElement(jdevices, i,
+ Common::Android::ToJString(env, devices[i].Serialize()));
+ }
+ return jdevices;
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_loadInputProfiles(JNIEnv* env,
+ jobject j_obj) {
+ map_profiles.clear();
+ const auto input_profile_loc =
+ Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "input";
+
+ if (Common::FS::IsDir(input_profile_loc)) {
+ Common::FS::IterateDirEntries(
+ input_profile_loc,
+ [&](const std::filesystem::path& full_path) {
+ const auto filename = full_path.filename();
+ const auto name_without_ext =
+ Common::FS::PathToUTF8String(GetNameWithoutExtension(filename));
+
+ if (filename.extension() == ".ini" && IsProfileNameValid(name_without_ext)) {
+ map_profiles.insert_or_assign(
+ name_without_ext, std::make_unique<AndroidConfig>(
+ name_without_ext, Config::ConfigType::InputProfile));
+ }
+
+ return true;
+ },
+ Common::FS::DirEntryFilter::File);
+ }
+}
+
+jobjectArray Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getInputProfileNames(
+ JNIEnv* env, jobject j_obj) {
+ std::vector<std::string> profile_names;
+ profile_names.reserve(map_profiles.size());
+
+ auto it = map_profiles.cbegin();
+ while (it != map_profiles.cend()) {
+ const auto& [profile_name, config] = *it;
+ if (!ProfileExistsInFilesystem(profile_name)) {
+ it = map_profiles.erase(it);
+ continue;
+ }
+
+ profile_names.push_back(profile_name);
+ ++it;
+ }
+
+ std::stable_sort(profile_names.begin(), profile_names.end());
+
+ jobjectArray j_profile_names =
+ env->NewObjectArray(profile_names.size(), Common::Android::GetStringClass(),
+ Common::Android::ToJString(env, ""));
+ for (size_t i = 0; i < profile_names.size(); ++i) {
+ env->SetObjectArrayElement(j_profile_names, i,
+ Common::Android::ToJString(env, profile_names[i]));
+ }
+
+ return j_profile_names;
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_isProfileNameValid(JNIEnv* env,
+ jobject j_obj,
+ jstring j_name) {
+ return Common::Android::GetJString(env, j_name).find_first_of("<>:;\"/\\|,.!?*") ==
+ std::string::npos;
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_createProfile(JNIEnv* env,
+ jobject j_obj,
+ jstring j_name,
+ jint j_player_index) {
+ auto profile_name = Common::Android::GetJString(env, j_name);
+ if (ProfileExistsInMap(profile_name)) {
+ return false;
+ }
+
+ map_profiles.insert_or_assign(
+ profile_name,
+ std::make_unique<AndroidConfig>(profile_name, Config::ConfigType::InputProfile));
+
+ return SaveProfile(profile_name, j_player_index);
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_deleteProfile(JNIEnv* env,
+ jobject j_obj,
+ jstring j_name,
+ jint j_player_index) {
+ auto profile_name = Common::Android::GetJString(env, j_name);
+ if (!ProfileExistsInMap(profile_name)) {
+ return false;
+ }
+
+ if (!ProfileExistsInFilesystem(profile_name) ||
+ Common::FS::RemoveFile(map_profiles[profile_name]->GetConfigFilePath())) {
+ map_profiles.erase(profile_name);
+ }
+
+ Settings::values.players.GetValue()[j_player_index].profile_name = "";
+ return !ProfileExistsInMap(profile_name) && !ProfileExistsInFilesystem(profile_name);
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_loadProfile(JNIEnv* env, jobject j_obj,
+ jstring j_name,
+ jint j_player_index) {
+ auto profile_name = Common::Android::GetJString(env, j_name);
+ return LoadProfile(profile_name, j_player_index);
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_saveProfile(JNIEnv* env, jobject j_obj,
+ jstring j_name,
+ jint j_player_index) {
+ auto profile_name = Common::Android::GetJString(env, j_name);
+ return SaveProfile(profile_name, j_player_index);
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_loadPerGameConfiguration(
+ JNIEnv* env, jobject j_obj, jint j_player_index, jint j_selected_index,
+ jstring j_selected_profile_name) {
+ static constexpr size_t HANDHELD_INDEX = 8;
+
+ auto& hid_core = EmulationSession::GetInstance().System().HIDCore();
+ Settings::values.players.SetGlobal(false);
+
+ auto profile_name = Common::Android::GetJString(env, j_selected_profile_name);
+ auto* emulated_controller = hid_core.GetEmulatedControllerByIndex(j_player_index);
+
+ if (j_selected_index == 0) {
+ Settings::values.players.GetValue()[j_player_index].profile_name = "";
+ if (j_player_index == 0) {
+ Settings::values.players.GetValue()[HANDHELD_INDEX] = {};
+ }
+ Settings::values.players.SetGlobal(true);
+ emulated_controller->ReloadFromSettings();
+ return;
+ }
+ if (profile_name.empty()) {
+ return;
+ }
+ auto& player = Settings::values.players.GetValue()[j_player_index];
+ auto& global_player = Settings::values.players.GetValue(true)[j_player_index];
+ player.profile_name = profile_name;
+ global_player.profile_name = profile_name;
+ // Read from the profile into the custom player settings
+ LoadProfile(profile_name, j_player_index);
+ // Make sure the controller is connected
+ player.connected = true;
+
+ emulated_controller->ReloadFromSettings();
+
+ if (j_player_index > 0) {
+ return;
+ }
+ // Handle Handheld cases
+ auto& handheld_player = Settings::values.players.GetValue()[HANDHELD_INDEX];
+ auto* handheld_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
+ if (player.controller_type == Settings::ControllerType::Handheld) {
+ handheld_player = player;
+ } else {
+ handheld_player = {};
+ }
+ handheld_controller->ReloadFromSettings();
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_beginMapping(JNIEnv* env, jobject j_obj,
+ jint jtype) {
+ EmulationSession::GetInstance().GetInputSubsystem().BeginMapping(
+ static_cast<InputCommon::Polling::InputType>(jtype));
+}
+
+jstring Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getNextInput(JNIEnv* env,
+ jobject j_obj) {
+ return Common::Android::ToJString(
+ env, EmulationSession::GetInstance().GetInputSubsystem().GetNextInput().Serialize());
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_stopMapping(JNIEnv* env, jobject j_obj) {
+ EmulationSession::GetInstance().GetInputSubsystem().StopMapping();
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_updateMappingsWithDefaultImpl(
+ JNIEnv* env, jobject j_obj, jint j_player_index, jstring j_device_params,
+ jstring j_display_name) {
+ auto& input_subsystem = EmulationSession::GetInstance().GetInputSubsystem();
+
+ // Clear all previous mappings
+ for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
+ ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
+ controller->SetButtonParam(button_id, {});
+ });
+ }
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
+ ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
+ controller->SetStickParam(analog_id, {});
+ });
+ }
+
+ // Apply new mappings
+ auto device = Common::ParamPackage(Common::Android::GetJString(env, j_device_params));
+ auto button_mappings = input_subsystem.GetButtonMappingForDevice(device);
+ auto analog_mappings = input_subsystem.GetAnalogMappingForDevice(device);
+ auto display_name = Common::Android::GetJString(env, j_display_name);
+ for (const auto& button_mapping : button_mappings) {
+ const std::size_t index = button_mapping.first;
+ auto named_mapping = button_mapping.second;
+ named_mapping.Set("display", display_name);
+ ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
+ controller->SetButtonParam(index, named_mapping);
+ });
+ }
+ for (const auto& analog_mapping : analog_mappings) {
+ const std::size_t index = analog_mapping.first;
+ auto named_mapping = analog_mapping.second;
+ named_mapping.Set("display", display_name);
+ ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
+ controller->SetStickParam(index, named_mapping);
+ });
+ }
+}
+
+jstring Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getButtonParamImpl(JNIEnv* env,
+ jobject j_obj,
+ jint j_player_index,
+ jint j_button) {
+ return Common::Android::ToJString(env, EmulationSession::GetInstance()
+ .System()
+ .HIDCore()
+ .GetEmulatedControllerByIndex(j_player_index)
+ ->GetButtonParam(j_button)
+ .Serialize());
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_setButtonParamImpl(
+ JNIEnv* env, jobject j_obj, jint j_player_index, jint j_button_id, jstring j_param) {
+ ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
+ controller->SetButtonParam(j_button_id,
+ Common::ParamPackage(Common::Android::GetJString(env, j_param)));
+ });
+}
+
+jstring Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getStickParamImpl(JNIEnv* env,
+ jobject j_obj,
+ jint j_player_index,
+ jint j_stick) {
+ return Common::Android::ToJString(env, EmulationSession::GetInstance()
+ .System()
+ .HIDCore()
+ .GetEmulatedControllerByIndex(j_player_index)
+ ->GetStickParam(j_stick)
+ .Serialize());
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_setStickParamImpl(
+ JNIEnv* env, jobject j_obj, jint j_player_index, jint j_stick_id, jstring j_param) {
+ ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
+ controller->SetStickParam(j_stick_id,
+ Common::ParamPackage(Common::Android::GetJString(env, j_param)));
+ });
+}
+
+jint Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getButtonNameImpl(JNIEnv* env,
+ jobject j_obj,
+ jstring j_param) {
+ return static_cast<jint>(EmulationSession::GetInstance().GetInputSubsystem().GetButtonName(
+ Common::ParamPackage(Common::Android::GetJString(env, j_param))));
+}
+
+jintArray Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getSupportedStyleTagsImpl(
+ JNIEnv* env, jobject j_obj, jint j_player_index) {
+ auto& hid_core = EmulationSession::GetInstance().System().HIDCore();
+ const auto npad_style_set = hid_core.GetSupportedStyleTag();
+ std::vector<s32> supported_indexes;
+ if (npad_style_set.fullkey == 1) {
+ supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::Fullkey));
+ }
+
+ if (npad_style_set.joycon_dual == 1) {
+ supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::JoyconDual));
+ }
+
+ if (npad_style_set.joycon_left == 1) {
+ supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::JoyconLeft));
+ }
+
+ if (npad_style_set.joycon_right == 1) {
+ supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::JoyconRight));
+ }
+
+ if (j_player_index == 0 && npad_style_set.handheld == 1) {
+ supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::Handheld));
+ }
+
+ if (npad_style_set.gamecube == 1) {
+ supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::GameCube));
+ }
+
+ jintArray j_supported_indexes = env->NewIntArray(supported_indexes.size());
+ env->SetIntArrayRegion(j_supported_indexes, 0, supported_indexes.size(),
+ supported_indexes.data());
+ return j_supported_indexes;
+}
+
+jint Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getStyleIndexImpl(JNIEnv* env,
+ jobject j_obj,
+ jint j_player_index) {
+ return static_cast<s32>(EmulationSession::GetInstance()
+ .System()
+ .HIDCore()
+ .GetEmulatedControllerByIndex(j_player_index)
+ ->GetNpadStyleIndex(true));
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_setStyleIndexImpl(JNIEnv* env,
+ jobject j_obj,
+ jint j_player_index,
+ jint j_style_index) {
+ auto& hid_core = EmulationSession::GetInstance().System().HIDCore();
+ auto type = static_cast<Core::HID::NpadStyleIndex>(j_style_index);
+ ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
+ controller->SetNpadStyleIndex(type);
+ });
+ if (j_player_index == 0) {
+ auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
+ auto* player_one = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
+ ConnectController(j_player_index,
+ player_one->IsConnected(true) || handheld->IsConnected(true));
+ }
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_isControllerImpl(JNIEnv* env,
+ jobject j_obj,
+ jstring jparams) {
+ return static_cast<jint>(EmulationSession::GetInstance().GetInputSubsystem().IsController(
+ Common::ParamPackage(Common::Android::GetJString(env, jparams))));
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getIsConnected(JNIEnv* env,
+ jobject j_obj,
+ jint j_player_index) {
+ auto& hid_core = EmulationSession::GetInstance().System().HIDCore();
+ auto* controller = hid_core.GetEmulatedControllerByIndex(static_cast<size_t>(j_player_index));
+ if (j_player_index == 0 &&
+ controller->GetNpadStyleIndex(true) == Core::HID::NpadStyleIndex::Handheld) {
+ return hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld)->IsConnected(true);
+ }
+ return controller->IsConnected(true);
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_connectControllersImpl(
+ JNIEnv* env, jobject j_obj, jbooleanArray j_connected) {
+ jboolean isCopy = false;
+ auto j_connected_array_size = env->GetArrayLength(j_connected);
+ jboolean* j_connected_array = env->GetBooleanArrayElements(j_connected, &isCopy);
+ for (int i = 0; i < j_connected_array_size; ++i) {
+ ConnectController(i, j_connected_array[i]);
+ }
+}
+
+void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_resetControllerMappings(
+ JNIEnv* env, jobject j_obj, jint j_player_index) {
+ // Clear all previous mappings
+ for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
+ ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
+ controller->SetButtonParam(button_id, {});
+ });
+ }
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
+ ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
+ controller->SetStickParam(analog_id, {});
+ });
+ }
+}
+
+} // extern "C"
diff --git a/src/android/app/src/main/res/drawable/button_anim.xml b/src/android/app/src/main/res/drawable/button_anim.xml
new file mode 100644
index 000000000..ccdc5ca6a
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_anim.xml
@@ -0,0 +1,142 @@
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="1000dp"
+ android:height="1000dp"
+ android:viewportWidth="1000"
+ android:viewportHeight="1000">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:pivotX="100"
+ android:pivotY="100"
+ android:scaleX="4.5"
+ android:scaleY="4.5"
+ android:translateX="400"
+ android:translateY="400">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="?attr/colorSecondaryContainer"
+ android:fillType="nonZero"
+ android:pathData=" M198.56 100 C198.56,154.43 154.43,198.56 100,198.56 C45.57,198.56 1.44,154.43 1.44,100 C1.44,45.57 45.57,1.44 100,1.44 C154.43,1.44 198.56,45.57 198.56,100c " />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:fillAlpha="0.8"
+ android:fillColor="?attr/colorOnSecondaryContainer"
+ android:fillType="nonZero"
+ android:pathData=" M50.14 151.21 C50.53,150.18 89.6,49.87 90.1,48.63 C90.1,48.63 90.67,47.2 90.67,47.2 C90.67,47.2 101.67,47.2 101.67,47.2 C101.67,47.2 112.67,47.2 112.67,47.2 C112.67,47.2 133.47,99.12 133.47,99.12 C144.91,127.68 154.32,151.17 154.38,151.33 C154.47,151.56 152.2,151.6 143.14,151.55 C143.14,151.55 131.79,151.48 131.79,151.48 C131.79,151.48 127.22,139.57 127.22,139.57 C127.22,139.57 122.65,127.66 122.65,127.66 C122.65,127.66 101.68,127.73 101.68,127.73 C101.68,127.73 80.71,127.8 80.71,127.8 C80.71,127.8 76.38,139.71 76.38,139.71 C76.38,139.71 72.06,151.62 72.06,151.62 C72.06,151.62 61.02,151.62 61.02,151.62 C50.61,151.62 50,151.55 50.14,151.22 C50.14,151.22 50.14,151.21 50.14,151.21c M115.86 110.06 C115.8,109.91 112.55,101.13 108.62,90.56 C104.7,80 101.42,71.43 101.34,71.53 C101.22,71.66 92.84,94.61 87.25,110.06 C87.17,110.29 90.13,110.34 101.56,110.34 C113,110.34 115.95,110.28 115.86,110.06c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="100"
+ android:propertyName="scaleX"
+ android:startOffset="0"
+ android:valueFrom="4.5"
+ android:valueTo="3.75"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="100"
+ android:propertyName="scaleY"
+ android:startOffset="0"
+ android:valueFrom="4.5"
+ android:valueTo="3.75"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="234"
+ android:propertyName="scaleX"
+ android:startOffset="100"
+ android:valueFrom="3.75"
+ android:valueTo="3.75"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="234"
+ android:propertyName="scaleY"
+ android:startOffset="100"
+ android:valueFrom="3.75"
+ android:valueTo="3.75"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="scaleX"
+ android:startOffset="334"
+ android:valueFrom="3.75"
+ android:valueTo="4.75"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="scaleY"
+ android:startOffset="334"
+ android:valueFrom="3.75"
+ android:valueTo="4.75"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="67"
+ android:propertyName="scaleX"
+ android:startOffset="501"
+ android:valueFrom="4.75"
+ android:valueTo="4.5"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="67"
+ android:propertyName="scaleY"
+ android:startOffset="501"
+ android:valueFrom="4.75"
+ android:valueTo="4.5"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="1034"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
diff --git a/src/android/app/src/main/res/drawable/ic_controller_disconnected.xml b/src/android/app/src/main/res/drawable/ic_controller_disconnected.xml
new file mode 100644
index 000000000..8e3c66f74
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_controller_disconnected.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M700,480q-25,0 -42.5,-17.5T640,420q0,-25 17.5,-42.5T700,360q25,0 42.5,17.5T760,420q0,25 -17.5,42.5T700,480ZM366,480ZM280,600v-80h-80v-80h80v-80h80v80h80v80h-80v80h-80ZM160,720q-33,0 -56.5,-23.5T80,640v-320q0,-34 24,-57.5t58,-23.5h77l81,81L160,320v320h366L55,169l57,-57 736,736 -57,57 -185,-185L160,720ZM880,640q0,26 -14,46t-37,29l-29,-29v-366L434,320l-80,-80h446q33,0 56.5,23.5T880,320v320ZM617,503Z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_more_vert.xml b/src/android/app/src/main/res/drawable/ic_more_vert.xml
new file mode 100644
index 000000000..9f62ac595
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_more_vert.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24"
+ android:width="24dp">
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_new_label.xml b/src/android/app/src/main/res/drawable/ic_new_label.xml
new file mode 100644
index 000000000..fac562c26
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_new_label.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M21,12l-4.37,6.16C16.26,18.68 15.65,19 15,19h-3l0,-6H9v-3H3V7c0,-1.1 0.9,-2 2,-2h10c0.65,0 1.26,0.31 1.63,0.84L21,12zM10,15H7v-3H5v3H2v2h3v3h2v-3h3V15z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_overlay.xml b/src/android/app/src/main/res/drawable/ic_overlay.xml
new file mode 100644
index 000000000..c7986c5a2
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_overlay.xml
@@ -0,0 +1,21 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M21,5H3C1.9,5 1,5.9 1,7v10c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V7C23,5.9 22.1,5 21,5zM18,17H6V7h12V17z" />
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M15,11.25h1.5v1.5h-1.5z" />
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M12.5,11.25h1.5v1.5h-1.5z" />
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M10,11.25h1.5v1.5h-1.5z" />
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M7.5,11.25h1.5v1.5h-1.5z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_share.xml b/src/android/app/src/main/res/drawable/ic_share.xml
new file mode 100644
index 000000000..3fc2f3c99
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_share.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/stick_one_direction_anim.xml b/src/android/app/src/main/res/drawable/stick_one_direction_anim.xml
new file mode 100644
index 000000000..a1da1316f
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/stick_one_direction_anim.xml
@@ -0,0 +1,118 @@
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="1000dp"
+ android:height="1000dp"
+ android:viewportWidth="1000"
+ android:viewportHeight="1000">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:pivotX="100"
+ android:pivotY="100"
+ android:scaleX="5"
+ android:scaleY="5"
+ android:translateX="400"
+ android:translateY="400">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:pathData=" M100 199.39 C59.8,199.39 23.56,175.17 8.18,138.04 C-7.2,100.9 1.3,58.15 29.73,29.72 C58.15,1.3 100.9,-7.21 138.04,8.18 C175.18,23.56 199.39,59.8 199.39,100 C199.33,154.87 154.87,199.33 100,199.39c "
+ android:strokeWidth="1"
+ android:strokeAlpha="0.6"
+ android:strokeColor="?attr/colorOutline"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round" />
+ </group>
+ <group
+ android:name="_R_G_L_0_G_T_1"
+ android:scaleX="5"
+ android:scaleY="5"
+ android:translateX="500"
+ android:translateY="500">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="-100"
+ android:translateY="-100">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="?attr/colorSecondaryContainer"
+ android:fillType="nonZero"
+ android:pathData=" M100.45 28.02 C140.63,28.02 173.2,60.59 173.2,100.77 C173.2,140.95 140.63,173.52 100.45,173.52 C60.27,173.52 27.7,140.95 27.7,100.77 C27.7,60.59 60.27,28.02 100.45,28.02c " />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:fillAlpha="0.8"
+ android:fillColor="?attr/colorOnSecondaryContainer"
+ android:fillType="nonZero"
+ android:pathData=" M100.45 50.26 C128.62,50.26 151.46,73.1 151.46,101.28 C151.46,129.45 128.62,152.29 100.45,152.29 C72.27,152.29 49.43,129.45 49.43,101.28 C49.43,73.1 72.27,50.26 100.45,50.26c " />
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="267"
+ android:pathData="M 500,500C 500,500 364,500 364,500"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="0">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="234"
+ android:pathData="M 364,500C 364,500 364,500 364,500"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="267">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0.333 0.667,0.667 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="133"
+ android:pathData="M 364,500C 364,500 525,500 525,500"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="501">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="100"
+ android:pathData="M 525,500C 525,500 500,500 500,500"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="634">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="968"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
diff --git a/src/android/app/src/main/res/drawable/stick_two_direction_anim.xml b/src/android/app/src/main/res/drawable/stick_two_direction_anim.xml
new file mode 100644
index 000000000..bc71adcbd
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/stick_two_direction_anim.xml
@@ -0,0 +1,173 @@
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="1000dp"
+ android:height="1000dp"
+ android:viewportWidth="1000"
+ android:viewportHeight="1000">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:pivotX="100"
+ android:pivotY="100"
+ android:scaleX="5"
+ android:scaleY="5"
+ android:translateX="400"
+ android:translateY="400">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:pathData=" M100 199.39 C59.8,199.39 23.56,175.17 8.18,138.04 C-7.2,100.9 1.3,58.15 29.73,29.72 C58.15,1.3 100.9,-7.21 138.04,8.18 C175.18,23.56 199.39,59.8 199.39,100 C199.33,154.87 154.87,199.33 100,199.39c "
+ android:strokeWidth="1"
+ android:strokeAlpha="0.6"
+ android:strokeColor="?attr/colorOutline"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round" />
+ </group>
+ <group
+ android:name="_R_G_L_0_G_T_1"
+ android:scaleX="5"
+ android:scaleY="5"
+ android:translateX="500"
+ android:translateY="500">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="-100"
+ android:translateY="-100">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="?attr/colorSecondaryContainer"
+ android:fillType="nonZero"
+ android:pathData=" M100.45 28.02 C140.63,28.02 173.2,60.59 173.2,100.77 C173.2,140.95 140.63,173.52 100.45,173.52 C60.27,173.52 27.7,140.95 27.7,100.77 C27.7,60.59 60.27,28.02 100.45,28.02c " />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:fillAlpha="0.8"
+ android:fillColor="?attr/colorOnSecondaryContainer"
+ android:fillType="nonZero"
+ android:pathData=" M100.45 50.26 C128.62,50.26 151.46,73.1 151.46,101.28 C151.46,129.45 128.62,152.29 100.45,152.29 C72.27,152.29 49.43,129.45 49.43,101.28 C49.43,73.1 72.27,50.26 100.45,50.26c " />
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="267"
+ android:pathData="M 500,500C 500,500 364,500 364,500"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="0">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="234"
+ android:pathData="M 364,500C 364,500 364,500 364,500"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="267">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0.333 0.667,0.667 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="133"
+ android:pathData="M 364,500C 364,500 525,500 525,500"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="501">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="100"
+ android:pathData="M 525,500C 525,500 500,500 500,500"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="634">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="400"
+ android:pathData="M 500,500C 500,500 500,500 500,500"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="734">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0.333 0.667,0.667 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="267"
+ android:pathData="M 500,500C 500,500 500,364 500,364"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="1134">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="234"
+ android:pathData="M 500,364C 500,364 500,364 500,364"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="1401">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0.333 0.667,0.667 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="133"
+ android:pathData="M 500,364C 500,364 500,535 500,535"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="1635">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="100"
+ android:pathData="M 500,535C 500,535 500,500 500,500"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="1768">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="2269"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
diff --git a/src/android/app/src/main/res/layout-ldrtl/list_item_setting_input.xml b/src/android/app/src/main/res/layout-ldrtl/list_item_setting_input.xml
new file mode 100644
index 000000000..583620dc6
--- /dev/null
+++ b/src/android/app/src/main/res/layout-ldrtl/list_item_setting_input.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/setting_body"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:clickable="true"
+ android:focusable="true"
+ android:gravity="center_vertical"
+ android:minHeight="72dp"
+ android:padding="16dp"
+ android:nextFocusLeft="@id/button_options">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_weight="1">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_name"
+ style="@style/TextAppearance.Material3.HeadlineMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart"
+ android:textSize="17sp"
+ app:lineHeight="22dp"
+ tools:text="Setting Name" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_value"
+ style="@style/TextAppearance.Material3.LabelMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/spacing_small"
+ android:textAlignment="viewStart"
+ android:textStyle="bold"
+ android:textSize="13sp"
+ tools:text="1x" />
+
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/button_options"
+ style="?attr/materialIconButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:nextFocusRight="@id/setting_body"
+ app:icon="@drawable/ic_more_vert"
+ app:iconSize="24dp"
+ app:iconTint="?attr/colorOnSurface" />
+
+ </LinearLayout>
+
+</RelativeLayout>
diff --git a/src/android/app/src/main/res/layout/card_driver_option.xml b/src/android/app/src/main/res/layout/card_driver_option.xml
index bda524f0f..09e26990b 100644
--- a/src/android/app/src/main/res/layout/card_driver_option.xml
+++ b/src/android/app/src/main/res/layout/card_driver_option.xml
@@ -39,10 +39,7 @@
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:ellipsize="none"
- android:marqueeRepeatLimit="marquee_forever"
android:requiresFadingEdge="horizontal"
- android:singleLine="true"
android:textAlignment="viewStart"
tools:text="@string/select_gpu_driver_default" />
@@ -52,10 +49,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
- android:ellipsize="none"
- android:marqueeRepeatLimit="marquee_forever"
android:requiresFadingEdge="horizontal"
- android:singleLine="true"
android:textAlignment="viewStart"
tools:text="@string/install_gpu_driver_description" />
@@ -65,10 +59,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
- android:ellipsize="none"
- android:marqueeRepeatLimit="marquee_forever"
android:requiresFadingEdge="horizontal"
- android:singleLine="true"
android:textAlignment="viewStart"
tools:text="@string/install_gpu_driver_description" />
diff --git a/src/android/app/src/main/res/layout/card_folder.xml b/src/android/app/src/main/res/layout/card_folder.xml
index ed4a7ca8f..e3a5f1a86 100644
--- a/src/android/app/src/main/res/layout/card_folder.xml
+++ b/src/android/app/src/main/res/layout/card_folder.xml
@@ -21,10 +21,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
- android:ellipsize="none"
- android:marqueeRepeatLimit="marquee_forever"
android:requiresFadingEdge="horizontal"
- android:singleLine="true"
android:textAlignment="viewStart"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/button_layout"
diff --git a/src/android/app/src/main/res/layout/card_game.xml b/src/android/app/src/main/res/layout/card_game.xml
index 6340171ec..411b50315 100644
--- a/src/android/app/src/main/res/layout/card_game.xml
+++ b/src/android/app/src/main/res/layout/card_game.xml
@@ -40,10 +40,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
- android:ellipsize="none"
- android:marqueeRepeatLimit="marquee_forever"
android:requiresFadingEdge="horizontal"
- android:singleLine="true"
android:textAlignment="center"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="@+id/image_game_screen"
diff --git a/src/android/app/src/main/res/layout/card_simple_outlined.xml b/src/android/app/src/main/res/layout/card_simple_outlined.xml
index b73930e7e..e29df6a2d 100644
--- a/src/android/app/src/main/res/layout/card_simple_outlined.xml
+++ b/src/android/app/src/main/res/layout/card_simple_outlined.xml
@@ -59,9 +59,6 @@
android:textAlignment="viewStart"
android:textSize="14sp"
android:textStyle="bold"
- android:singleLine="true"
- android:marqueeRepeatLimit="marquee_forever"
- android:ellipsize="none"
android:requiresFadingEdge="horizontal"
android:layout_marginTop="6dp"
android:visibility="gone"
diff --git a/src/android/app/src/main/res/layout/dialog_input_profiles.xml b/src/android/app/src/main/res/layout/dialog_input_profiles.xml
new file mode 100644
index 000000000..6ad76fe41
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_input_profiles.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list_profiles"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fadeScrollbars="false" />
diff --git a/src/android/app/src/main/res/layout/dialog_mapping.xml b/src/android/app/src/main/res/layout/dialog_mapping.xml
new file mode 100644
index 000000000..06190b8d2
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_mapping.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:defaultFocusHighlightEnabled="false"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:focusedByDefault="true"
+ android:orientation="horizontal"
+ android:gravity="center">
+
+ <ImageView
+ android:id="@+id/image_stick_animation"
+ android:layout_width="@dimen/mapping_anim_size"
+ android:layout_height="@dimen/mapping_anim_size"
+ tools:src="@drawable/stick_two_direction_anim" />
+
+ <ImageView
+ android:id="@+id/image_button_animation"
+ android:layout_width="@dimen/mapping_anim_size"
+ android:layout_height="@dimen/mapping_anim_size"
+ android:layout_marginStart="48dp"
+ tools:src="@drawable/button_anim" />
+
+</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_game_properties.xml b/src/android/app/src/main/res/layout/fragment_game_properties.xml
index 436ebd79d..5e3f3cf28 100644
--- a/src/android/app/src/main/res/layout/fragment_game_properties.xml
+++ b/src/android/app/src/main/res/layout/fragment_game_properties.xml
@@ -76,10 +76,7 @@
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:layout_marginHorizontal="16dp"
- android:ellipsize="none"
- android:marqueeRepeatLimit="marquee_forever"
android:requiresFadingEdge="horizontal"
- android:singleLine="true"
android:textAlignment="center"
tools:text="deko_basic" />
diff --git a/src/android/app/src/main/res/layout/list_item_input_profile.xml b/src/android/app/src/main/res/layout/list_item_input_profile.xml
new file mode 100644
index 000000000..a08dccf0c
--- /dev/null
+++ b/src/android/app/src/main/res/layout/list_item_input_profile.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:focusable="false"
+ android:paddingHorizontal="20dp"
+ android:paddingVertical="16dp">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.Material3.HeadlineMedium"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:textAlignment="viewStart"
+ android:gravity="start|center_vertical"
+ android:textSize="17sp"
+ android:layout_marginEnd="16dp"
+ app:layout_constraintBottom_toBottomOf="@+id/button_layout"
+ app:layout_constraintEnd_toStartOf="@+id/button_layout"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:lineHeight="28dp"
+ tools:text="My profile" />
+
+ <LinearLayout
+ android:id="@+id/button_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <Button
+ android:id="@+id/button_new"
+ style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/create_new_profile"
+ android:tooltipText="@string/create_new_profile"
+ app:icon="@drawable/ic_new_label" />
+
+ <Button
+ android:id="@+id/button_delete"
+ style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/delete"
+ android:tooltipText="@string/delete"
+ app:icon="@drawable/ic_delete" />
+
+ <Button
+ android:id="@+id/button_save"
+ style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/save"
+ android:tooltipText="@string/save"
+ app:icon="@drawable/ic_save" />
+
+ <Button
+ android:id="@+id/button_load"
+ style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/load"
+ android:tooltipText="@string/load"
+ app:icon="@drawable/ic_import" />
+
+ </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout/list_item_setting_input.xml b/src/android/app/src/main/res/layout/list_item_setting_input.xml
new file mode 100644
index 000000000..d67cbe245
--- /dev/null
+++ b/src/android/app/src/main/res/layout/list_item_setting_input.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/setting_body"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:clickable="true"
+ android:focusable="true"
+ android:gravity="center_vertical"
+ android:minHeight="72dp"
+ android:padding="16dp"
+ android:nextFocusRight="@id/button_options">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_weight="1">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_name"
+ style="@style/TextAppearance.Material3.HeadlineMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart"
+ android:textSize="17sp"
+ app:lineHeight="22dp"
+ tools:text="Setting Name" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_value"
+ style="@style/TextAppearance.Material3.LabelMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/spacing_small"
+ android:textAlignment="viewStart"
+ android:textStyle="bold"
+ android:textSize="13sp"
+ tools:text="1x" />
+
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/button_options"
+ style="?attr/materialIconButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:nextFocusLeft="@id/setting_body"
+ app:icon="@drawable/ic_more_vert"
+ app:iconSize="24dp"
+ app:iconTint="?attr/colorOnSurface" />
+
+ </LinearLayout>
+
+</RelativeLayout>
diff --git a/src/android/app/src/main/res/menu/menu_in_game.xml b/src/android/app/src/main/res/menu/menu_in_game.xml
index eecb0563b..867197ebc 100644
--- a/src/android/app/src/main/res/menu/menu_in_game.xml
+++ b/src/android/app/src/main/res/menu/menu_in_game.xml
@@ -17,8 +17,13 @@
android:title="@string/per_game_settings" />
<item
- android:id="@+id/menu_overlay_controls"
+ android:id="@+id/menu_controls"
android:icon="@drawable/ic_controller"
+ android:title="@string/preferences_controls" />
+
+ <item
+ android:id="@+id/menu_overlay_controls"
+ android:icon="@drawable/ic_overlay"
android:title="@string/emulation_input_overlay" />
<item
diff --git a/src/android/app/src/main/res/menu/menu_input_options.xml b/src/android/app/src/main/res/menu/menu_input_options.xml
new file mode 100644
index 000000000..81ea5043f
--- /dev/null
+++ b/src/android/app/src/main/res/menu/menu_input_options.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item
+ android:id="@+id/invert_axis"
+ android:title="@string/invert_axis"
+ android:visible="false" />
+
+ <item
+ android:id="@+id/invert_button"
+ android:title="@string/invert_button"
+ android:visible="false" />
+
+ <item
+ android:id="@+id/toggle_button"
+ android:title="@string/toggle_button"
+ android:visible="false" />
+
+ <item
+ android:id="@+id/turbo_button"
+ android:title="@string/turbo_button"
+ android:visible="false" />
+
+ <item
+ android:id="@+id/set_threshold"
+ android:title="@string/set_threshold"
+ android:visible="false" />
+
+ <item
+ android:id="@+id/toggle_axis"
+ android:title="@string/toggle_axis"
+ android:visible="false" />
+
+</menu>
diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml
index 1d87d36b3..e4c66e7d5 100644
--- a/src/android/app/src/main/res/navigation/settings_navigation.xml
+++ b/src/android/app/src/main/res/navigation/settings_navigation.xml
@@ -26,7 +26,7 @@
<fragment
android:id="@+id/settingsSearchFragment"
- android:name="org.yuzu.yuzu_emu.fragments.SettingsSearchFragment"
+ android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsSearchFragment"
android:label="SettingsSearchFragment" />
</navigation>
diff --git a/src/android/app/src/main/res/values-w600dp/dimens.xml b/src/android/app/src/main/res/values-w600dp/dimens.xml
index 128319e27..0e2d40876 100644
--- a/src/android/app/src/main/res/values-w600dp/dimens.xml
+++ b/src/android/app/src/main/res/values-w600dp/dimens.xml
@@ -2,4 +2,6 @@
<resources>
<dimen name="spacing_navigation">0dp</dimen>
<dimen name="spacing_navigation_rail">80dp</dimen>
+
+ <dimen name="mapping_anim_size">100dp</dimen>
</resources>
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml
index 992b5ae44..bf733637f 100644
--- a/src/android/app/src/main/res/values/dimens.xml
+++ b/src/android/app/src/main/res/values/dimens.xml
@@ -18,4 +18,6 @@
<dimen name="dialog_margin">20dp</dimen>
<dimen name="elevated_app_bar">3dp</dimen>
+
+ <dimen name="mapping_anim_size">75dp</dimen>
</resources>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 78a4c958a..6a631f664 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -255,6 +255,92 @@
<string name="audio_volume">Volume</string>
<string name="audio_volume_description">Specifies the volume of audio output.</string>
+ <!-- Input strings -->
+ <string name="buttons">Buttons</string>
+ <string name="button_a">A</string>
+ <string name="button_b">B</string>
+ <string name="button_x">X</string>
+ <string name="button_y">Y</string>
+ <string name="button_plus">Plus</string>
+ <string name="button_minus">Minus</string>
+ <string name="button_home">Home</string>
+ <string name="button_capture">Capture</string>
+ <string name="start_pause">Start/Pause</string>
+ <string name="dpad">D-Pad</string>
+ <string name="up">Up</string>
+ <string name="down">Down</string>
+ <string name="left">Left</string>
+ <string name="right">Right</string>
+ <string name="left_stick">Left stick</string>
+ <string name="control_stick">Control stick</string>
+ <string name="right_stick">Right stick</string>
+ <string name="c_stick">C-Stick</string>
+ <string name="pressed">Pressed</string>
+ <string name="range">Range</string>
+ <string name="deadzone">Deadzone</string>
+ <string name="modifier">Modifier</string>
+ <string name="modifier_range">Modifier range</string>
+ <string name="triggers">Triggers</string>
+ <string name="button_l">L</string>
+ <string name="button_r">R</string>
+ <string name="button_zl">ZL</string>
+ <string name="button_zr">ZR</string>
+ <string name="button_sl_left">Left SL</string>
+ <string name="button_sr_left">Left SR</string>
+ <string name="button_sl_right">Right SL</string>
+ <string name="button_sr_right">Right SR</string>
+ <string name="button_z">Z</string>
+ <string name="invalid">Invalid</string>
+ <string name="not_set">Not set</string>
+ <string name="unknown">Unknown</string>
+ <string name="qualified_hat">%1$s%2$s%3$sHat %4$s</string>
+ <string name="qualified_button_stick_axis">%1$s%2$s%3$sAxis %4$s</string>
+ <string name="qualified_button">%1$s%2$s%3$sButton %4$s</string>
+ <string name="qualified_axis">Axis %1$s%2$s</string>
+ <string name="unused">Unused</string>
+ <string name="input_prompt">Move or press an input</string>
+ <string name="unsupported_input">Unsupported input type</string>
+ <string name="input_mapping_filter">Input mapping filter</string>
+ <string name="input_mapping_filter_description">Select a device to filter mapping inputs</string>
+ <string name="auto_map">Auto-map a controller</string>
+ <string name="auto_map_description">Select a device to attempt auto-mapping</string>
+ <string name="attempted_auto_map">Attempted auto-map with %1$s</string>
+ <string name="controller_type">Controller type</string>
+ <string name="pro_controller">Pro Controller</string>
+ <string name="handheld">Handheld</string>
+ <string name="dual_joycons">Dual Joycons</string>
+ <string name="left_joycon">Left Joycon</string>
+ <string name="right_joycon">Right Joycon</string>
+ <string name="gamecube_controller">GameCube Controller</string>
+ <string name="invert_axis">Invert axis</string>
+ <string name="invert_button">Invert button</string>
+ <string name="toggle_button">Toggle button</string>
+ <string name="turbo_button">Turbo button</string>
+ <string name="set_threshold">Set threshold</string>
+ <string name="toggle_axis">Toggle axis</string>
+ <string name="connected">Connected</string>
+ <string name="use_system_vibrator">Use system vibrator</string>
+ <string name="input_overlay">Input overlay</string>
+ <string name="vibration">Vibration</string>
+ <string name="vibration_strength">Vibration strength</string>
+ <string name="profile">Profile</string>
+ <string name="create_new_profile">Create new profile</string>
+ <string name="enter_profile_name">Enter profile name</string>
+ <string name="profile_name_already_exists">Profile name already exists</string>
+ <string name="invalid_profile_name">Invalid profile name</string>
+ <string name="use_global_input_configuration">Use global input configuration</string>
+ <string name="player_num_profile">Player %d profile</string>
+ <string name="delete_input_profile">Delete input profile</string>
+ <string name="delete_input_profile_description">Are you sure that you want to delete this profile? This is not recoverable.</string>
+ <string name="stick_map_description">Move a stick left and then up or press a button</string>
+ <string name="button_map_description">Press a button or move a trigger/stick</string>
+ <string name="map_dpad_direction">Map to D-Pad %1$s</string>
+ <string name="map_control">Map to %1$s</string>
+ <string name="failed_to_load_profile">Failed to load profile</string>
+ <string name="failed_to_save_profile">Failed to save profile</string>
+ <string name="reset_mapping">Reset mappings</string>
+ <string name="reset_mapping_description">Are you sure that you want to reset all mappings for this controller to default? This cannot be undone.</string>
+
<!-- Miscellaneous -->
<string name="slider_default">Default</string>
<string name="ini_saved">Saved settings</string>
@@ -292,6 +378,10 @@
<string name="more_options">More options</string>
<string name="use_global_setting">Use global setting</string>
<string name="operation_completed_successfully">The operation completed successfully</string>
+ <string name="retry">Retry</string>
+ <string name="confirm">Confirm</string>
+ <string name="load">Load</string>
+ <string name="save">Save</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Select GPU driver</string>
@@ -313,6 +403,9 @@
<string name="preferences_graphics_description">Accuracy level, resolution, shader cache</string>
<string name="preferences_audio">Audio</string>
<string name="preferences_audio_description">Output engine, volume</string>
+ <string name="preferences_controls">Controls</string>
+ <string name="preferences_controls_description">Map controller input</string>
+ <string name="preferences_player">Player %d</string>
<string name="preferences_theme">Theme and color</string>
<string name="preferences_debug">Debug</string>
<string name="preferences_debug_description">CPU/GPU debugging, graphics API, fastmem</string>
diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp
index d97ca2a40..49efae8e3 100644
--- a/src/audio_core/sink/cubeb_sink.cpp
+++ b/src/audio_core/sink/cubeb_sink.cpp
@@ -357,7 +357,9 @@ bool IsCubebSuitable() {
return false;
}
- SCOPE_EXIT({ cubeb_destroy(ctx); });
+ SCOPE_EXIT {
+ cubeb_destroy(ctx);
+ };
#ifdef _WIN32
if (SUCCEEDED(com_init_result)) {
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
index c047b0668..0a98eb31e 100644
--- a/src/audio_core/sink/sink_stream.cpp
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -20,10 +20,10 @@
namespace AudioCore::Sink {
void SinkStream::AppendBuffer(SinkBuffer& buffer, std::span<s16> samples) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
queue.enqueue(buffer);
++queued_buffers;
- });
+ };
if (type == StreamType::In) {
return;
diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp
index f39262db9..1145cbdf2 100644
--- a/src/common/android/id_cache.cpp
+++ b/src/common/android/id_cache.cpp
@@ -65,6 +65,30 @@ static jclass s_boolean_class;
static jmethodID s_boolean_constructor;
static jfieldID s_boolean_value_field;
+static jclass s_player_input_class;
+static jmethodID s_player_input_constructor;
+static jfieldID s_player_input_connected_field;
+static jfieldID s_player_input_buttons_field;
+static jfieldID s_player_input_analogs_field;
+static jfieldID s_player_input_motions_field;
+static jfieldID s_player_input_vibration_enabled_field;
+static jfieldID s_player_input_vibration_strength_field;
+static jfieldID s_player_input_body_color_left_field;
+static jfieldID s_player_input_body_color_right_field;
+static jfieldID s_player_input_button_color_left_field;
+static jfieldID s_player_input_button_color_right_field;
+static jfieldID s_player_input_profile_name_field;
+static jfieldID s_player_input_use_system_vibrator_field;
+
+static jclass s_yuzu_input_device_interface;
+static jmethodID s_yuzu_input_device_get_name;
+static jmethodID s_yuzu_input_device_get_guid;
+static jmethodID s_yuzu_input_device_get_port;
+static jmethodID s_yuzu_input_device_get_supports_vibration;
+static jmethodID s_yuzu_input_device_vibrate;
+static jmethodID s_yuzu_input_device_get_axes;
+static jmethodID s_yuzu_input_device_has_keys;
+
static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
namespace Common::Android {
@@ -276,6 +300,94 @@ jfieldID GetBooleanValueField() {
return s_boolean_value_field;
}
+jclass GetPlayerInputClass() {
+ return s_player_input_class;
+}
+
+jmethodID GetPlayerInputConstructor() {
+ return s_player_input_constructor;
+}
+
+jfieldID GetPlayerInputConnectedField() {
+ return s_player_input_connected_field;
+}
+
+jfieldID GetPlayerInputButtonsField() {
+ return s_player_input_buttons_field;
+}
+
+jfieldID GetPlayerInputAnalogsField() {
+ return s_player_input_analogs_field;
+}
+
+jfieldID GetPlayerInputMotionsField() {
+ return s_player_input_motions_field;
+}
+
+jfieldID GetPlayerInputVibrationEnabledField() {
+ return s_player_input_vibration_enabled_field;
+}
+
+jfieldID GetPlayerInputVibrationStrengthField() {
+ return s_player_input_vibration_strength_field;
+}
+
+jfieldID GetPlayerInputBodyColorLeftField() {
+ return s_player_input_body_color_left_field;
+}
+
+jfieldID GetPlayerInputBodyColorRightField() {
+ return s_player_input_body_color_right_field;
+}
+
+jfieldID GetPlayerInputButtonColorLeftField() {
+ return s_player_input_button_color_left_field;
+}
+
+jfieldID GetPlayerInputButtonColorRightField() {
+ return s_player_input_button_color_right_field;
+}
+
+jfieldID GetPlayerInputProfileNameField() {
+ return s_player_input_profile_name_field;
+}
+
+jfieldID GetPlayerInputUseSystemVibratorField() {
+ return s_player_input_use_system_vibrator_field;
+}
+
+jclass GetYuzuInputDeviceInterface() {
+ return s_yuzu_input_device_interface;
+}
+
+jmethodID GetYuzuDeviceGetName() {
+ return s_yuzu_input_device_get_name;
+}
+
+jmethodID GetYuzuDeviceGetGUID() {
+ return s_yuzu_input_device_get_guid;
+}
+
+jmethodID GetYuzuDeviceGetPort() {
+ return s_yuzu_input_device_get_port;
+}
+
+jmethodID GetYuzuDeviceGetSupportsVibration() {
+ return s_yuzu_input_device_get_supports_vibration;
+}
+
+jmethodID GetYuzuDeviceVibrate() {
+ return s_yuzu_input_device_vibrate;
+}
+
+jmethodID GetYuzuDeviceGetAxes() {
+ return s_yuzu_input_device_get_axes;
+}
+
+jmethodID GetYuzuDeviceHasKeys() {
+ return s_yuzu_input_device_has_keys;
+}
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -387,6 +499,55 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z");
env->DeleteLocalRef(boolean_class);
+ const jclass player_input_class =
+ env->FindClass("org/yuzu/yuzu_emu/features/input/model/PlayerInput");
+ s_player_input_class = reinterpret_cast<jclass>(env->NewGlobalRef(player_input_class));
+ s_player_input_constructor = env->GetMethodID(
+ player_input_class, "<init>",
+ "(Z[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;ZIJJJJLjava/lang/String;Z)V");
+ s_player_input_connected_field = env->GetFieldID(player_input_class, "connected", "Z");
+ s_player_input_buttons_field =
+ env->GetFieldID(player_input_class, "buttons", "[Ljava/lang/String;");
+ s_player_input_analogs_field =
+ env->GetFieldID(player_input_class, "analogs", "[Ljava/lang/String;");
+ s_player_input_motions_field =
+ env->GetFieldID(player_input_class, "motions", "[Ljava/lang/String;");
+ s_player_input_vibration_enabled_field =
+ env->GetFieldID(player_input_class, "vibrationEnabled", "Z");
+ s_player_input_vibration_strength_field =
+ env->GetFieldID(player_input_class, "vibrationStrength", "I");
+ s_player_input_body_color_left_field =
+ env->GetFieldID(player_input_class, "bodyColorLeft", "J");
+ s_player_input_body_color_right_field =
+ env->GetFieldID(player_input_class, "bodyColorRight", "J");
+ s_player_input_button_color_left_field =
+ env->GetFieldID(player_input_class, "buttonColorLeft", "J");
+ s_player_input_button_color_right_field =
+ env->GetFieldID(player_input_class, "buttonColorRight", "J");
+ s_player_input_profile_name_field =
+ env->GetFieldID(player_input_class, "profileName", "Ljava/lang/String;");
+ s_player_input_use_system_vibrator_field =
+ env->GetFieldID(player_input_class, "useSystemVibrator", "Z");
+ env->DeleteLocalRef(player_input_class);
+
+ const jclass yuzu_input_device_interface =
+ env->FindClass("org/yuzu/yuzu_emu/features/input/YuzuInputDevice");
+ s_yuzu_input_device_interface =
+ reinterpret_cast<jclass>(env->NewGlobalRef(yuzu_input_device_interface));
+ s_yuzu_input_device_get_name =
+ env->GetMethodID(yuzu_input_device_interface, "getName", "()Ljava/lang/String;");
+ s_yuzu_input_device_get_guid =
+ env->GetMethodID(yuzu_input_device_interface, "getGUID", "()Ljava/lang/String;");
+ s_yuzu_input_device_get_port = env->GetMethodID(yuzu_input_device_interface, "getPort", "()I");
+ s_yuzu_input_device_get_supports_vibration =
+ env->GetMethodID(yuzu_input_device_interface, "getSupportsVibration", "()Z");
+ s_yuzu_input_device_vibrate = env->GetMethodID(yuzu_input_device_interface, "vibrate", "(F)V");
+ s_yuzu_input_device_get_axes =
+ env->GetMethodID(yuzu_input_device_interface, "getAxes", "()[Ljava/lang/Integer;");
+ s_yuzu_input_device_has_keys =
+ env->GetMethodID(yuzu_input_device_interface, "hasKeys", "([I)[Z");
+ env->DeleteLocalRef(yuzu_input_device_interface);
+
// Initialize Android Storage
Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
@@ -416,6 +577,8 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
env->DeleteGlobalRef(s_double_class);
env->DeleteGlobalRef(s_integer_class);
env->DeleteGlobalRef(s_boolean_class);
+ env->DeleteGlobalRef(s_player_input_class);
+ env->DeleteGlobalRef(s_yuzu_input_device_interface);
// UnInitialize applets
SoftwareKeyboard::CleanupJNI(env);
diff --git a/src/common/android/id_cache.h b/src/common/android/id_cache.h
index 47802f96c..cd2844dcc 100644
--- a/src/common/android/id_cache.h
+++ b/src/common/android/id_cache.h
@@ -85,4 +85,28 @@ jclass GetBooleanClass();
jmethodID GetBooleanConstructor();
jfieldID GetBooleanValueField();
+jclass GetPlayerInputClass();
+jmethodID GetPlayerInputConstructor();
+jfieldID GetPlayerInputConnectedField();
+jfieldID GetPlayerInputButtonsField();
+jfieldID GetPlayerInputAnalogsField();
+jfieldID GetPlayerInputMotionsField();
+jfieldID GetPlayerInputVibrationEnabledField();
+jfieldID GetPlayerInputVibrationStrengthField();
+jfieldID GetPlayerInputBodyColorLeftField();
+jfieldID GetPlayerInputBodyColorRightField();
+jfieldID GetPlayerInputButtonColorLeftField();
+jfieldID GetPlayerInputButtonColorRightField();
+jfieldID GetPlayerInputProfileNameField();
+jfieldID GetPlayerInputUseSystemVibratorField();
+
+jclass GetYuzuInputDeviceInterface();
+jmethodID GetYuzuDeviceGetName();
+jmethodID GetYuzuDeviceGetGUID();
+jmethodID GetYuzuDeviceGetPort();
+jmethodID GetYuzuDeviceGetSupportsVibration();
+jmethodID GetYuzuDeviceVibrate();
+jmethodID GetYuzuDeviceGetAxes();
+jmethodID GetYuzuDeviceHasKeys();
+
} // namespace Common::Android
diff --git a/src/common/demangle.cpp b/src/common/demangle.cpp
index 6e117cb41..b2c9d126a 100644
--- a/src/common/demangle.cpp
+++ b/src/common/demangle.cpp
@@ -20,7 +20,9 @@ std::string DemangleSymbol(const std::string& mangled) {
}
char* demangled = nullptr;
- SCOPE_EXIT({ std::free(demangled); });
+ SCOPE_EXIT {
+ std::free(demangled);
+ };
if (is_itanium(mangled)) {
demangled = llvm::itaniumDemangle(mangled.c_str());
diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp
index 860c39e6a..e0b5a6a67 100644
--- a/src/common/host_memory.cpp
+++ b/src/common/host_memory.cpp
@@ -430,11 +430,11 @@ public:
explicit Impl(size_t backing_size_, size_t virtual_size_)
: backing_size{backing_size_}, virtual_size{virtual_size_} {
bool good = false;
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (!good) {
Release();
}
- });
+ };
long page_size = sysconf(_SC_PAGESIZE);
if (page_size != 0x1000) {
diff --git a/src/common/page_table.cpp b/src/common/page_table.cpp
index 85dc18c11..3205eb7da 100644
--- a/src/common/page_table.cpp
+++ b/src/common/page_table.cpp
@@ -24,10 +24,10 @@ bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* c
out_entry->block_size = page_size;
// Regardless of whether the page was mapped, advance on exit.
- SCOPE_EXIT({
+ SCOPE_EXIT {
context->next_page += 1;
context->next_offset += page_size;
- });
+ };
// Validate that we can read the actual entry.
const auto page = context->next_page;
diff --git a/src/common/scope_exit.h b/src/common/scope_exit.h
index e9c789c88..f3e88cde9 100644
--- a/src/common/scope_exit.h
+++ b/src/common/scope_exit.h
@@ -7,29 +7,61 @@
#include "common/common_funcs.h"
namespace detail {
-template <typename Func>
-struct ScopeExitHelper {
- explicit ScopeExitHelper(Func&& func_) : func(std::move(func_)) {}
- ~ScopeExitHelper() {
+template <class F>
+class ScopeGuard {
+ YUZU_NON_COPYABLE(ScopeGuard);
+
+private:
+ F f;
+ bool active;
+
+public:
+ constexpr ScopeGuard(F f_) : f(std::move(f_)), active(true) {}
+ constexpr ~ScopeGuard() {
if (active) {
- func();
+ f();
}
}
-
- void Cancel() {
+ constexpr void Cancel() {
active = false;
}
- Func func;
- bool active{true};
+ constexpr ScopeGuard(ScopeGuard&& rhs) : f(std::move(rhs.f)), active(rhs.active) {
+ rhs.Cancel();
+ }
+
+ ScopeGuard& operator=(ScopeGuard&& rhs) = delete;
};
-template <typename Func>
-ScopeExitHelper<Func> ScopeExit(Func&& func) {
- return ScopeExitHelper<Func>(std::forward<Func>(func));
+template <class F>
+constexpr ScopeGuard<F> MakeScopeGuard(F f) {
+ return ScopeGuard<F>(std::move(f));
}
+
+enum class ScopeGuardOnExit {};
+
+template <typename F>
+constexpr ScopeGuard<F> operator+(ScopeGuardOnExit, F&& f) {
+ return ScopeGuard<F>(std::forward<F>(f));
+}
+
} // namespace detail
+#define CONCATENATE_IMPL(s1, s2) s1##s2
+#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
+
+#ifdef __COUNTER__
+#define ANONYMOUS_VARIABLE(pref) CONCATENATE(pref, __COUNTER__)
+#else
+#define ANONYMOUS_VARIABLE(pref) CONCATENATE(pref, __LINE__)
+#endif
+
+/**
+ * This macro is similar to SCOPE_EXIT, except the object is caller managed. This is intended to be
+ * used when the caller might want to cancel the ScopeExit.
+ */
+#define SCOPE_GUARD detail::ScopeGuardOnExit() + [&]()
+
/**
* This macro allows you to conveniently specify a block of code that will run on scope exit. Handy
* for doing ad-hoc clean-up tasks in a function with multiple returns.
@@ -38,7 +70,7 @@ ScopeExitHelper<Func> ScopeExit(Func&& func) {
* \code
* const int saved_val = g_foo;
* g_foo = 55;
- * SCOPE_EXIT({ g_foo = saved_val; });
+ * SCOPE_EXIT{ g_foo = saved_val; };
*
* if (Bar()) {
* return 0;
@@ -47,10 +79,4 @@ ScopeExitHelper<Func> ScopeExit(Func&& func) {
* }
* \endcode
*/
-#define SCOPE_EXIT(body) auto CONCAT2(scope_exit_helper_, __LINE__) = detail::ScopeExit([&]() body)
-
-/**
- * This macro is similar to SCOPE_EXIT, except the object is caller managed. This is intended to be
- * used when the caller might want to cancel the ScopeExit.
- */
-#define SCOPE_GUARD(body) detail::ScopeExit([&]() body)
+#define SCOPE_EXIT auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_) = SCOPE_GUARD
diff --git a/src/common/settings_input.h b/src/common/settings_input.h
index 53a95ef8f..a99bb0892 100644
--- a/src/common/settings_input.h
+++ b/src/common/settings_input.h
@@ -395,6 +395,10 @@ struct PlayerInput {
u32 button_color_left;
u32 button_color_right;
std::string profile_name;
+
+ // This is meant to tell the Android frontend whether to use a device's built-in vibration
+ // motor or a controller's vibrations.
+ bool use_system_vibrator;
};
struct TouchscreenInput {
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 93c548942..f67a12f8f 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -2,8 +2,8 @@
# SPDX-License-Identifier: GPL-2.0-or-later
add_library(core STATIC
- arm/arm_interface.h
arm/arm_interface.cpp
+ arm/arm_interface.h
arm/debug.cpp
arm/debug.h
arm/exclusive_monitor.cpp
@@ -37,10 +37,10 @@ add_library(core STATIC
debugger/gdbstub.h
debugger/gdbstub_arch.cpp
debugger/gdbstub_arch.h
- device_memory_manager.h
- device_memory_manager.inc
device_memory.cpp
device_memory.h
+ device_memory_manager.h
+ device_memory_manager.inc
file_sys/bis_factory.cpp
file_sys/bis_factory.h
file_sys/card_image.cpp
@@ -390,6 +390,20 @@ add_library(core STATIC
hle/service/acc/errors.h
hle/service/acc/profile_manager.cpp
hle/service/acc/profile_manager.h
+ hle/service/am/am.cpp
+ hle/service/am/am.h
+ hle/service/am/am_results.h
+ hle/service/am/am_types.h
+ hle/service/am/applet.cpp
+ hle/service/am/applet.h
+ hle/service/am/applet_data_broker.cpp
+ hle/service/am/applet_data_broker.h
+ hle/service/am/applet_manager.cpp
+ hle/service/am/applet_manager.h
+ hle/service/am/applet_message_queue.cpp
+ hle/service/am/applet_message_queue.h
+ hle/service/am/display_layer_manager.cpp
+ hle/service/am/display_layer_manager.h
hle/service/am/frontend/applet_cabinet.cpp
hle/service/am/frontend/applet_cabinet.h
hle/service/am/frontend/applet_controller.cpp
@@ -411,24 +425,10 @@ add_library(core STATIC
hle/service/am/frontend/applet_web_browser_types.h
hle/service/am/frontend/applets.cpp
hle/service/am/frontend/applets.h
- hle/service/am/am.cpp
- hle/service/am/am.h
- hle/service/am/am_results.h
- hle/service/am/am_types.h
- hle/service/am/applet.cpp
- hle/service/am/applet.h
- hle/service/am/applet_manager.cpp
- hle/service/am/applet_data_broker.cpp
- hle/service/am/applet_data_broker.h
- hle/service/am/applet_manager.h
- hle/service/am/applet_message_queue.cpp
- hle/service/am/applet_message_queue.h
hle/service/am/hid_registration.cpp
hle/service/am/hid_registration.h
hle/service/am/library_applet_storage.cpp
hle/service/am/library_applet_storage.h
- hle/service/am/managed_layer_holder.cpp
- hle/service/am/managed_layer_holder.h
hle/service/am/process.cpp
hle/service/am/process.h
hle/service/am/service/all_system_applet_proxies_service.cpp
@@ -441,10 +441,10 @@ add_library(core STATIC
hle/service/am/service/application_creator.h
hle/service/am/service/application_functions.cpp
hle/service/am/service/application_functions.h
- hle/service/am/service/application_proxy_service.cpp
- hle/service/am/service/application_proxy_service.h
hle/service/am/service/application_proxy.cpp
hle/service/am/service/application_proxy.h
+ hle/service/am/service/application_proxy_service.cpp
+ hle/service/am/service/application_proxy_service.h
hle/service/am/service/audio_controller.cpp
hle/service/am/service/audio_controller.h
hle/service/am/service/common_state_getter.cpp
@@ -473,16 +473,14 @@ add_library(core STATIC
hle/service/am/service/process_winding_controller.h
hle/service/am/service/self_controller.cpp
hle/service/am/service/self_controller.h
- hle/service/am/service/storage_accessor.cpp
- hle/service/am/service/storage_accessor.h
hle/service/am/service/storage.cpp
hle/service/am/service/storage.h
+ hle/service/am/service/storage_accessor.cpp
+ hle/service/am/service/storage_accessor.h
hle/service/am/service/system_applet_proxy.cpp
hle/service/am/service/system_applet_proxy.h
hle/service/am/service/window_controller.cpp
hle/service/am/service/window_controller.h
- hle/service/am/system_buffer_manager.cpp
- hle/service/am/system_buffer_manager.h
hle/service/aoc/aoc_u.cpp
hle/service/aoc/aoc_u.h
hle/service/apm/apm.cpp
@@ -491,12 +489,12 @@ add_library(core STATIC
hle/service/apm/apm_controller.h
hle/service/apm/apm_interface.cpp
hle/service/apm/apm_interface.h
- hle/service/audio/audctl.cpp
- hle/service/audio/audctl.h
hle/service/audio/audin_u.cpp
hle/service/audio/audin_u.h
hle/service/audio/audio.cpp
hle/service/audio/audio.h
+ hle/service/audio/audio_controller.cpp
+ hle/service/audio/audio_controller.h
hle/service/audio/audout_u.cpp
hle/service/audio/audout_u.h
hle/service/audio/audrec_a.cpp
@@ -510,18 +508,6 @@ add_library(core STATIC
hle/service/audio/hwopus.h
hle/service/bcat/backend/backend.cpp
hle/service/bcat/backend/backend.h
- hle/service/bcat/news/newly_arrived_event_holder.cpp
- hle/service/bcat/news/newly_arrived_event_holder.h
- hle/service/bcat/news/news_data_service.cpp
- hle/service/bcat/news/news_data_service.h
- hle/service/bcat/news/news_database_service.cpp
- hle/service/bcat/news/news_database_service.h
- hle/service/bcat/news/news_service.cpp
- hle/service/bcat/news/news_service.h
- hle/service/bcat/news/overwrite_event_holder.cpp
- hle/service/bcat/news/overwrite_event_holder.h
- hle/service/bcat/news/service_creator.cpp
- hle/service/bcat/news/service_creator.h
hle/service/bcat/bcat.cpp
hle/service/bcat/bcat.h
hle/service/bcat/bcat_result.h
@@ -537,6 +523,18 @@ add_library(core STATIC
hle/service/bcat/delivery_cache_progress_service.h
hle/service/bcat/delivery_cache_storage_service.cpp
hle/service/bcat/delivery_cache_storage_service.h
+ hle/service/bcat/news/newly_arrived_event_holder.cpp
+ hle/service/bcat/news/newly_arrived_event_holder.h
+ hle/service/bcat/news/news_data_service.cpp
+ hle/service/bcat/news/news_data_service.h
+ hle/service/bcat/news/news_database_service.cpp
+ hle/service/bcat/news/news_database_service.h
+ hle/service/bcat/news/news_service.cpp
+ hle/service/bcat/news/news_service.h
+ hle/service/bcat/news/overwrite_event_holder.cpp
+ hle/service/bcat/news/overwrite_event_holder.h
+ hle/service/bcat/news/service_creator.cpp
+ hle/service/bcat/news/service_creator.h
hle/service/bcat/service_creator.cpp
hle/service/bcat/service_creator.h
hle/service/bpc/bpc.cpp
@@ -610,8 +608,6 @@ add_library(core STATIC
hle/service/filesystem/romfs_controller.h
hle/service/filesystem/save_data_controller.cpp
hle/service/filesystem/save_data_controller.h
- hle/service/fgm/fgm.cpp
- hle/service/fgm/fgm.h
hle/service/friend/friend.cpp
hle/service/friend/friend.h
hle/service/friend/friend_interface.cpp
@@ -749,15 +745,48 @@ add_library(core STATIC
hle/service/nim/nim.h
hle/service/npns/npns.cpp
hle/service/npns/npns.h
- hle/service/ns/errors.h
- hle/service/ns/iplatform_service_manager.cpp
- hle/service/ns/iplatform_service_manager.h
+ hle/service/ns/account_proxy_interface.cpp
+ hle/service/ns/account_proxy_interface.h
+ hle/service/ns/application_manager_interface.cpp
+ hle/service/ns/application_manager_interface.h
+ hle/service/ns/application_version_interface.cpp
+ hle/service/ns/application_version_interface.h
+ hle/service/ns/content_management_interface.cpp
+ hle/service/ns/content_management_interface.h
+ hle/service/ns/develop_interface.cpp
+ hle/service/ns/develop_interface.h
+ hle/service/ns/document_interface.cpp
+ hle/service/ns/document_interface.h
+ hle/service/ns/download_task_interface.cpp
+ hle/service/ns/download_task_interface.h
+ hle/service/ns/dynamic_rights_interface.cpp
+ hle/service/ns/dynamic_rights_interface.h
+ hle/service/ns/ecommerce_interface.cpp
+ hle/service/ns/ecommerce_interface.h
+ hle/service/ns/factory_reset_interface.cpp
+ hle/service/ns/factory_reset_interface.h
hle/service/ns/language.cpp
hle/service/ns/language.h
hle/service/ns/ns.cpp
hle/service/ns/ns.h
- hle/service/ns/pdm_qry.cpp
- hle/service/ns/pdm_qry.h
+ hle/service/ns/ns_results.h
+ hle/service/ns/ns_types.h
+ hle/service/ns/platform_service_manager.cpp
+ hle/service/ns/platform_service_manager.h
+ hle/service/ns/query_service.cpp
+ hle/service/ns/query_service.h
+ hle/service/ns/read_only_application_control_data_interface.cpp
+ hle/service/ns/read_only_application_control_data_interface.h
+ hle/service/ns/read_only_application_record_interface.cpp
+ hle/service/ns/read_only_application_record_interface.h
+ hle/service/ns/service_getter_interface.cpp
+ hle/service/ns/service_getter_interface.h
+ hle/service/ns/system_update_control.cpp
+ hle/service/ns/system_update_control.h
+ hle/service/ns/system_update_interface.cpp
+ hle/service/ns/system_update_interface.h
+ hle/service/ns/vulnerability_manager_interface.cpp
+ hle/service/ns/vulnerability_manager_interface.h
hle/service/nvdrv/core/container.cpp
hle/service/nvdrv/core/container.h
hle/service/nvdrv/core/heap_mapper.cpp
@@ -810,14 +839,14 @@ add_library(core STATIC
hle/service/nvnflinger/consumer_base.cpp
hle/service/nvnflinger/consumer_base.h
hle/service/nvnflinger/consumer_listener.h
- hle/service/nvnflinger/fb_share_buffer_manager.cpp
- hle/service/nvnflinger/fb_share_buffer_manager.h
hle/service/nvnflinger/graphic_buffer_producer.cpp
hle/service/nvnflinger/graphic_buffer_producer.h
- hle/service/nvnflinger/hos_binder_driver_server.cpp
- hle/service/nvnflinger/hos_binder_driver_server.h
hle/service/nvnflinger/hardware_composer.cpp
hle/service/nvnflinger/hardware_composer.h
+ hle/service/nvnflinger/hos_binder_driver.cpp
+ hle/service/nvnflinger/hos_binder_driver.h
+ hle/service/nvnflinger/hos_binder_driver_server.cpp
+ hle/service/nvnflinger/hos_binder_driver_server.h
hle/service/nvnflinger/hwc_layer.h
hle/service/nvnflinger/nvnflinger.cpp
hle/service/nvnflinger/nvnflinger.h
@@ -825,6 +854,8 @@ add_library(core STATIC
hle/service/nvnflinger/pixel_format.h
hle/service/nvnflinger/producer_listener.h
hle/service/nvnflinger/status.h
+ hle/service/nvnflinger/surface_flinger.cpp
+ hle/service/nvnflinger/surface_flinger.h
hle/service/nvnflinger/ui/fence.h
hle/service/nvnflinger/ui/graphic_buffer.cpp
hle/service/nvnflinger/ui/graphic_buffer.h
@@ -841,11 +872,11 @@ add_library(core STATIC
hle/service/omm/power_state_interface.h
hle/service/os/event.cpp
hle/service/os/event.h
+ hle/service/os/multi_wait.cpp
+ hle/service/os/multi_wait.h
hle/service/os/multi_wait_holder.cpp
hle/service/os/multi_wait_holder.h
hle/service/os/multi_wait_utils.h
- hle/service/os/multi_wait.cpp
- hle/service/os/multi_wait.h
hle/service/os/mutex.cpp
hle/service/os/mutex.h
hle/service/pcie/pcie.cpp
@@ -883,15 +914,17 @@ add_library(core STATIC
hle/service/psc/time/common.cpp
hle/service/psc/time/common.h
hle/service/psc/time/errors.h
- hle/service/psc/time/shared_memory.cpp
- hle/service/psc/time/shared_memory.h
- hle/service/psc/time/static.cpp
- hle/service/psc/time/static.h
hle/service/psc/time/manager.h
+ hle/service/psc/time/power_state_request_manager.cpp
+ hle/service/psc/time/power_state_request_manager.h
hle/service/psc/time/power_state_service.cpp
hle/service/psc/time/power_state_service.h
hle/service/psc/time/service_manager.cpp
hle/service/psc/time/service_manager.h
+ hle/service/psc/time/shared_memory.cpp
+ hle/service/psc/time/shared_memory.h
+ hle/service/psc/time/static.cpp
+ hle/service/psc/time/static.h
hle/service/psc/time/steady_clock.cpp
hle/service/psc/time/steady_clock.h
hle/service/psc/time/system_clock.cpp
@@ -900,8 +933,6 @@ add_library(core STATIC
hle/service/psc/time/time_zone.h
hle/service/psc/time/time_zone_service.cpp
hle/service/psc/time/time_zone_service.h
- hle/service/psc/time/power_state_request_manager.cpp
- hle/service/psc/time/power_state_request_manager.h
hle/service/ptm/psm.cpp
hle/service/ptm/psm.h
hle/service/ptm/ptm.cpp
@@ -918,19 +949,21 @@ add_library(core STATIC
hle/service/server_manager.h
hle/service/service.cpp
hle/service/service.h
+ hle/service/services.cpp
+ hle/service/services.h
+ hle/service/set/factory_settings_server.cpp
+ hle/service/set/factory_settings_server.h
+ hle/service/set/firmware_debug_settings_server.cpp
+ hle/service/set/firmware_debug_settings_server.h
+ hle/service/set/key_code_map.h
hle/service/set/setting_formats/appln_settings.cpp
hle/service/set/setting_formats/appln_settings.h
hle/service/set/setting_formats/device_settings.cpp
hle/service/set/setting_formats/device_settings.h
- hle/service/set/setting_formats/system_settings.cpp
- hle/service/set/setting_formats/system_settings.h
hle/service/set/setting_formats/private_settings.cpp
hle/service/set/setting_formats/private_settings.h
- hle/service/set/factory_settings_server.cpp
- hle/service/set/factory_settings_server.h
- hle/service/set/firmware_debug_settings_server.cpp
- hle/service/set/firmware_debug_settings_server.h
- hle/service/set/key_code_map.h
+ hle/service/set/setting_formats/system_settings.cpp
+ hle/service/set/setting_formats/system_settings.h
hle/service/set/settings.cpp
hle/service/set/settings.h
hle/service/set/settings_server.cpp
@@ -965,30 +998,36 @@ add_library(core STATIC
hle/service/ssl/ssl_backend.h
hle/service/usb/usb.cpp
hle/service/usb/usb.h
- hle/service/vi/display/vi_display.cpp
- hle/service/vi/display/vi_display.h
- hle/service/vi/layer/vi_layer.cpp
- hle/service/vi/layer/vi_layer.h
hle/service/vi/application_display_service.cpp
hle/service/vi/application_display_service.h
hle/service/vi/application_root_service.cpp
hle/service/vi/application_root_service.h
- hle/service/vi/hos_binder_driver.cpp
- hle/service/vi/hos_binder_driver.h
+ hle/service/vi/conductor.cpp
+ hle/service/vi/conductor.h
+ hle/service/vi/container.cpp
+ hle/service/vi/container.h
+ hle/service/vi/display.h
+ hle/service/vi/display_list.h
+ hle/service/vi/layer.h
+ hle/service/vi/layer_list.h
hle/service/vi/manager_display_service.cpp
hle/service/vi/manager_display_service.h
hle/service/vi/manager_root_service.cpp
hle/service/vi/manager_root_service.h
hle/service/vi/service_creator.cpp
hle/service/vi/service_creator.h
+ hle/service/vi/shared_buffer_manager.cpp
+ hle/service/vi/shared_buffer_manager.h
hle/service/vi/system_display_service.cpp
hle/service/vi/system_display_service.h
hle/service/vi/system_root_service.cpp
hle/service/vi/system_root_service.h
- hle/service/vi/vi_results.h
- hle/service/vi/vi_types.h
hle/service/vi/vi.cpp
hle/service/vi/vi.h
+ hle/service/vi/vi_results.h
+ hle/service/vi/vi_types.h
+ hle/service/vi/vsync_manager.cpp
+ hle/service/vi/vsync_manager.h
internal_network/network.cpp
internal_network/network.h
internal_network/network_interface.cpp
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 435ef6793..9e8936728 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -47,6 +47,7 @@
#include "core/hle/service/psc/time/system_clock.h"
#include "core/hle/service/psc/time/time_zone_service.h"
#include "core/hle/service/service.h"
+#include "core/hle/service/services.h"
#include "core/hle/service/set/system_settings_server.h"
#include "core/hle/service/sm/sm.h"
#include "core/internal_network/network.h"
@@ -242,7 +243,7 @@ struct System::Impl {
void Run() {
std::unique_lock<std::mutex> lk(suspend_guard);
- kernel.SuspendApplication(false);
+ kernel.SuspendEmulation(false);
core_timing.SyncPause(false);
is_paused.store(false, std::memory_order_relaxed);
}
@@ -251,7 +252,7 @@ struct System::Impl {
std::unique_lock<std::mutex> lk(suspend_guard);
core_timing.SyncPause(true);
- kernel.SuspendApplication(true);
+ kernel.SuspendEmulation(true);
is_paused.store(true, std::memory_order_relaxed);
}
@@ -261,7 +262,7 @@ struct System::Impl {
std::unique_lock<std::mutex> StallApplication() {
std::unique_lock<std::mutex> lk(suspend_guard);
- kernel.SuspendApplication(true);
+ kernel.SuspendEmulation(true);
core_timing.SyncPause(true);
return lk;
}
@@ -269,7 +270,7 @@ struct System::Impl {
void UnstallApplication() {
if (!IsPaused()) {
core_timing.SyncPause(false);
- kernel.SuspendApplication(false);
+ kernel.SuspendEmulation(false);
}
}
@@ -310,7 +311,8 @@ struct System::Impl {
audio_core = std::make_unique<AudioCore::AudioCore>(system);
service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
- services = std::make_unique<Service::Services>(service_manager, system);
+ services =
+ std::make_unique<Service::Services>(service_manager, system, stop_event.get_token());
is_powered_on = true;
exit_locked = false;
@@ -458,11 +460,10 @@ struct System::Impl {
gpu_core->NotifyShutdown();
}
+ stop_event.request_stop();
+ core_timing.SyncPause(false);
Network::CancelPendingSocketOperations();
- kernel.SuspendApplication(true);
- if (services) {
- services->KillNVNFlinger();
- }
+ kernel.SuspendEmulation(true);
kernel.CloseServices();
kernel.ShutdownCores();
applet_manager.Reset();
@@ -480,6 +481,7 @@ struct System::Impl {
cpu_manager.Shutdown();
debugger.reset();
kernel.Shutdown();
+ stop_event = {};
Network::RestartSocketOperations();
if (auto room_member = room_network.GetRoomMember().lock()) {
@@ -615,6 +617,7 @@ struct System::Impl {
ExecuteProgramCallback execute_program_callback;
ExitCallback exit_callback;
+ std::stop_source stop_event;
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_cpu{};
diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp
index 7a5c22f78..9b1c77387 100644
--- a/src/core/cpu_manager.cpp
+++ b/src/core/cpu_manager.cpp
@@ -199,10 +199,10 @@ void CpuManager::RunThread(std::stop_token token, std::size_t core) {
data.host_context = Common::Fiber::ThreadToFiber();
// Cleanup
- SCOPE_EXIT({
+ SCOPE_EXIT {
data.host_context->Exit();
MicroProfileOnThreadExit();
- });
+ };
// Running
if (!gpu_barrier->Sync(token)) {
diff --git a/src/core/device_memory_manager.inc b/src/core/device_memory_manager.inc
index 6dfee806c..37c1e69c3 100644
--- a/src/core/device_memory_manager.inc
+++ b/src/core/device_memory_manager.inc
@@ -391,12 +391,12 @@ void DeviceMemoryManager<Traits>::WalkBlock(DAddr addr, std::size_t size, auto o
std::min((next_pages << Memory::YUZU_PAGEBITS) - page_offset, remaining_size);
const auto current_vaddr =
static_cast<u64>((page_index << Memory::YUZU_PAGEBITS) + page_offset);
- SCOPE_EXIT({
+ SCOPE_EXIT{
page_index += next_pages;
page_offset = 0;
increment(copy_amount);
remaining_size -= copy_amount;
- });
+ };
auto phys_addr = compressed_physical_ptr[page_index];
if (phys_addr == 0) {
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index 555b9d8f7..667efbbab 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -64,8 +64,8 @@ struct RawNACP {
u64_le cache_storage_size;
u64_le cache_storage_journal_size;
u64_le cache_storage_data_and_journal_max_size;
- u64_le cache_storage_max_index;
- INSERT_PADDING_BYTES(0xE70);
+ u16_le cache_storage_max_index;
+ INSERT_PADDING_BYTES(0xE76);
};
static_assert(sizeof(RawNACP) == 0x4000, "RawNACP has incorrect size.");
diff --git a/src/core/file_sys/fs_directory.h b/src/core/file_sys/fs_directory.h
index 25c9cb18a..3f90abb8f 100644
--- a/src/core/file_sys/fs_directory.h
+++ b/src/core/file_sys/fs_directory.h
@@ -3,6 +3,10 @@
#pragma once
+#include <string_view>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
namespace FileSys {
constexpr inline size_t EntryNameLengthMax = 0x300;
diff --git a/src/core/file_sys/fs_path_utility.h b/src/core/file_sys/fs_path_utility.h
index e9011d065..5643141f9 100644
--- a/src/core/file_sys/fs_path_utility.h
+++ b/src/core/file_sys/fs_path_utility.h
@@ -447,7 +447,7 @@ public:
char* replacement_path = nullptr;
size_t replacement_path_size = 0;
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (replacement_path != nullptr) {
if (std::is_constant_evaluated()) {
delete[] replacement_path;
@@ -455,7 +455,7 @@ public:
Deallocate(replacement_path, replacement_path_size);
}
}
- });
+ };
// Perform path replacement, if necessary
if (IsParentDirectoryPathReplacementNeeded(cur_path)) {
@@ -1102,8 +1102,8 @@ public:
R_SUCCEED();
}
- static Result Normalize(char* dst, size_t dst_size, const char* path, size_t path_len,
- const PathFlags& flags) {
+ static constexpr Result Normalize(char* dst, size_t dst_size, const char* path, size_t path_len,
+ const PathFlags& flags) {
// Use StringTraits names for remainder of scope
using namespace StringTraits;
@@ -1199,7 +1199,7 @@ public:
const size_t replaced_src_len = path_len - (src - path);
char* replaced_src = nullptr;
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (replaced_src != nullptr) {
if (std::is_constant_evaluated()) {
delete[] replaced_src;
@@ -1207,7 +1207,7 @@ public:
Deallocate(replaced_src, replaced_src_len);
}
}
- });
+ };
if (std::is_constant_evaluated()) {
replaced_src = new char[replaced_src_len];
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp
index caea0b8f8..a68fd973c 100644
--- a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp
+++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp
@@ -36,7 +36,9 @@ Result HierarchicalSha256Storage::Initialize(VirtualFile* base_storages, s32 lay
// Get the base storage size.
m_base_storage_size = base_storages[2]->GetSize();
{
- auto size_guard = SCOPE_GUARD({ m_base_storage_size = 0; });
+ auto size_guard = SCOPE_GUARD {
+ m_base_storage_size = 0;
+ };
R_UNLESS(m_base_storage_size <= static_cast<s64>(HashSize)
<< m_log_size_ratio << m_log_size_ratio,
ResultHierarchicalSha256BaseStorageTooLarge);
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index ae4e441c9..289969cc4 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -98,7 +98,9 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
Loader::ResultStatus ProgramMetadata::Reload(VirtualFile file) {
const u64 original_program_id = aci_header.title_id;
- SCOPE_EXIT({ aci_header.title_id = original_program_id; });
+ SCOPE_EXIT {
+ aci_header.title_id = original_program_id;
+ };
return this->Load(file);
}
diff --git a/src/core/file_sys/system_archive/shared_font.cpp b/src/core/file_sys/system_archive/shared_font.cpp
index deb52069d..9ea16aa59 100644
--- a/src/core/file_sys/system_archive/shared_font.cpp
+++ b/src/core/file_sys/system_archive/shared_font.cpp
@@ -9,7 +9,7 @@
#include "core/file_sys/system_archive/data/font_standard.h"
#include "core/file_sys/system_archive/shared_font.h"
#include "core/file_sys/vfs/vfs_vector.h"
-#include "core/hle/service/ns/iplatform_service_manager.h"
+#include "core/hle/service/ns/platform_service_manager.h"
namespace FileSys::SystemArchive {
diff --git a/src/core/hle/kernel/k_client_session.cpp b/src/core/hle/kernel/k_client_session.cpp
index 472e8571c..3e01e3b67 100644
--- a/src/core/hle/kernel/k_client_session.cpp
+++ b/src/core/hle/kernel/k_client_session.cpp
@@ -24,7 +24,9 @@ Result KClientSession::SendSyncRequest(uintptr_t address, size_t size) {
// Create a session request.
KSessionRequest* request = KSessionRequest::Create(m_kernel);
R_UNLESS(request != nullptr, ResultOutOfResource);
- SCOPE_EXIT({ request->Close(); });
+ SCOPE_EXIT {
+ request->Close();
+ };
// Initialize the request.
request->Initialize(nullptr, address, size);
@@ -37,7 +39,9 @@ Result KClientSession::SendAsyncRequest(KEvent* event, uintptr_t address, size_t
// Create a session request.
KSessionRequest* request = KSessionRequest::Create(m_kernel);
R_UNLESS(request != nullptr, ResultOutOfResource);
- SCOPE_EXIT({ request->Close(); });
+ SCOPE_EXIT {
+ request->Close();
+ };
// Initialize the request.
request->Initialize(event, address, size);
diff --git a/src/core/hle/kernel/k_page_table_base.cpp b/src/core/hle/kernel/k_page_table_base.cpp
index 1dd86fb3c..19cdf4f3a 100644
--- a/src/core/hle/kernel/k_page_table_base.cpp
+++ b/src/core/hle/kernel/k_page_table_base.cpp
@@ -1305,11 +1305,11 @@ Result KPageTableBase::UnmapCodeMemory(KProcessAddress dst_address, KProcessAddr
// Ensure that we maintain the instruction cache.
bool reprotected_pages = false;
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (reprotected_pages && any_code_pages) {
InvalidateInstructionCache(m_kernel, this, dst_address, size);
}
- });
+ };
// Unmap.
{
@@ -1397,7 +1397,9 @@ Result KPageTableBase::MapInsecureMemory(KProcessAddress address, size_t size) {
// Close the opened pages when we're done with them.
// If the mapping succeeds, each page will gain an extra reference, otherwise they will be freed
// automatically.
- SCOPE_EXIT({ pg.Close(); });
+ SCOPE_EXIT {
+ pg.Close();
+ };
// Clear all the newly allocated pages.
for (const auto& it : pg) {
@@ -1603,7 +1605,9 @@ Result KPageTableBase::AllocateAndMapPagesImpl(PageLinkedList* page_list, KProce
m_kernel.MemoryManager().AllocateAndOpen(std::addressof(pg), num_pages, m_allocate_option));
// Ensure that the page group is closed when we're done working with it.
- SCOPE_EXIT({ pg.Close(); });
+ SCOPE_EXIT {
+ pg.Close();
+ };
// Clear all pages.
for (const auto& it : pg) {
@@ -2191,7 +2195,9 @@ Result KPageTableBase::SetHeapSize(KProcessAddress* out, size_t size) {
// Close the opened pages when we're done with them.
// If the mapping succeeds, each page will gain an extra reference, otherwise they will be freed
// automatically.
- SCOPE_EXIT({ pg.Close(); });
+ SCOPE_EXIT {
+ pg.Close();
+ };
// Clear all the newly allocated pages.
for (const auto& it : pg) {
@@ -2592,7 +2598,9 @@ Result KPageTableBase::UnmapIoRegion(KProcessAddress dst_address, KPhysicalAddre
// Temporarily unlock ourselves, so that other operations can occur while we flush the
// region.
m_general_lock.Unlock();
- SCOPE_EXIT({ m_general_lock.Lock(); });
+ SCOPE_EXIT {
+ m_general_lock.Lock();
+ };
// Flush the region.
R_ASSERT(FlushDataCache(dst_address, size));
@@ -3311,10 +3319,10 @@ Result KPageTableBase::ReadIoMemoryImpl(KProcessAddress dst_addr, KPhysicalAddre
// Ensure we unmap the io memory when we're done with it.
const KPageProperties unmap_properties =
KPageProperties{KMemoryPermission::None, false, false, DisableMergeAttribute::None};
- SCOPE_EXIT({
+ SCOPE_EXIT {
R_ASSERT(this->Operate(updater.GetPageList(), io_addr, map_size / PageSize, 0, false,
unmap_properties, OperationType::Unmap, true));
- });
+ };
// Read the memory.
const KProcessAddress read_addr = io_addr + (GetInteger(phys_addr) & (PageSize - 1));
@@ -3347,10 +3355,10 @@ Result KPageTableBase::WriteIoMemoryImpl(KPhysicalAddress phys_addr, KProcessAdd
// Ensure we unmap the io memory when we're done with it.
const KPageProperties unmap_properties =
KPageProperties{KMemoryPermission::None, false, false, DisableMergeAttribute::None};
- SCOPE_EXIT({
+ SCOPE_EXIT {
R_ASSERT(this->Operate(updater.GetPageList(), io_addr, map_size / PageSize, 0, false,
unmap_properties, OperationType::Unmap, true));
- });
+ };
// Write the memory.
const KProcessAddress write_addr = io_addr + (GetInteger(phys_addr) & (PageSize - 1));
@@ -4491,14 +4499,14 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size,
// If the partial pages are mapped, an extra reference will have been opened. Otherwise, they'll
// free on scope exit.
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (start_partial_page != 0) {
m_kernel.MemoryManager().Close(start_partial_page, 1);
}
if (end_partial_page != 0) {
m_kernel.MemoryManager().Close(end_partial_page, 1);
}
- });
+ };
ON_RESULT_FAILURE {
if (cur_mapped_addr != dst_addr) {
@@ -5166,10 +5174,10 @@ Result KPageTableBase::MapPhysicalMemory(KProcessAddress address, size_t size) {
GetCurrentProcess(m_kernel).GetId(), m_heap_fill_value));
// If we fail in the next bit (or retry), we need to cleanup the pages.
- auto pg_guard = SCOPE_GUARD({
+ auto pg_guard = SCOPE_GUARD {
pg.OpenFirst();
pg.Close();
- });
+ };
// Map the memory.
{
@@ -5694,7 +5702,9 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
// Ensure that any pages we track are closed on exit.
KPageGroup pages_to_close(m_kernel, this->GetBlockInfoManager());
- SCOPE_EXIT({ pages_to_close.CloseAndReset(); });
+ SCOPE_EXIT {
+ pages_to_close.CloseAndReset();
+ };
// Make a page group representing the region to unmap.
this->MakePageGroup(pages_to_close, virt_addr, num_pages);
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 1bcc42890..cb9a11a63 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -77,7 +77,9 @@ Result TerminateChildren(KernelCore& kernel, KProcess* process,
}
// Terminate and close the thread.
- SCOPE_EXIT({ cur_child->Close(); });
+ SCOPE_EXIT {
+ cur_child->Close();
+ };
if (const Result terminate_result = cur_child->Terminate();
ResultTerminationRequested == terminate_result) {
@@ -466,11 +468,11 @@ void KProcess::DoWorkerTaskImpl() {
Result KProcess::StartTermination() {
// Finalize the handle table when we're done, if the process isn't immortal.
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (!m_is_immortal) {
this->FinalizeHandleTable();
}
- });
+ };
// Terminate child threads other than the current one.
R_RETURN(TerminateChildren(m_kernel, this, GetCurrentThreadPointer(m_kernel)));
@@ -964,7 +966,9 @@ Result KProcess::Run(s32 priority, size_t stack_size) {
// Create a new thread for the process.
KThread* main_thread = KThread::Create(m_kernel);
R_UNLESS(main_thread != nullptr, ResultOutOfResource);
- SCOPE_EXIT({ main_thread->Close(); });
+ SCOPE_EXIT {
+ main_thread->Close();
+ };
// Initialize the thread.
R_TRY(KThread::InitializeUserThread(m_kernel.System(), main_thread, this->GetEntryPoint(), 0,
@@ -1155,7 +1159,9 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
Kernel::CreateResourceLimitForProcess(m_kernel.System(), physical_memory_size);
// Ensure we maintain a clean state on exit.
- SCOPE_EXIT({ res_limit->Close(); });
+ SCOPE_EXIT {
+ res_limit->Close();
+ };
// Declare flags and code address.
Svc::CreateProcessFlag flag{};
diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp
index adaabdd6d..40c3323ef 100644
--- a/src/core/hle/kernel/k_server_session.cpp
+++ b/src/core/hle/kernel/k_server_session.cpp
@@ -651,11 +651,11 @@ Result ReceiveMessage(KernelCore& kernel, bool& recv_list_broken, uint64_t dst_m
// Process any special data.
if (src_header.GetHasSpecialHeader()) {
// After we process, make sure we track whether the receive list is broken.
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (offset > dst_recv_list_idx) {
recv_list_broken = true;
}
- });
+ };
// Process special data.
R_TRY(ProcessMessageSpecialData<false>(offset, dst_process, src_process, src_thread,
@@ -665,11 +665,11 @@ Result ReceiveMessage(KernelCore& kernel, bool& recv_list_broken, uint64_t dst_m
// Process any pointer buffers.
for (auto i = 0; i < src_header.GetPointerCount(); ++i) {
// After we process, make sure we track whether the receive list is broken.
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (offset > dst_recv_list_idx) {
recv_list_broken = true;
}
- });
+ };
R_TRY(ProcessReceiveMessagePointerDescriptors(
offset, pointer_key, dst_page_table, src_page_table, dst_msg, src_msg, dst_recv_list,
@@ -680,11 +680,11 @@ Result ReceiveMessage(KernelCore& kernel, bool& recv_list_broken, uint64_t dst_m
// Process any map alias buffers.
for (auto i = 0; i < src_header.GetMapAliasCount(); ++i) {
// After we process, make sure we track whether the receive list is broken.
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (offset > dst_recv_list_idx) {
recv_list_broken = true;
}
- });
+ };
// We process in order send, recv, exch. Buffers after send (recv/exch) are ReadWrite.
const KMemoryPermission perm = (i >= src_header.GetSendCount())
@@ -702,11 +702,11 @@ Result ReceiveMessage(KernelCore& kernel, bool& recv_list_broken, uint64_t dst_m
// Process any raw data.
if (const auto raw_count = src_header.GetRawCount(); raw_count != 0) {
// After we process, make sure we track whether the receive list is broken.
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (offset + raw_count > dst_recv_list_idx) {
recv_list_broken = true;
}
- });
+ };
// Get the offset and size.
const size_t offset_words = offset * sizeof(u32);
@@ -1124,7 +1124,9 @@ Result KServerSession::ReceiveRequest(uintptr_t server_message, uintptr_t server
client_thread->Open();
}
- SCOPE_EXIT({ client_thread->Close(); });
+ SCOPE_EXIT {
+ client_thread->Close();
+ };
// Set the request as our current.
m_current_request = request;
@@ -1174,7 +1176,9 @@ Result KServerSession::ReceiveRequest(uintptr_t server_message, uintptr_t server
// Reply to the client.
{
// After we reply, close our reference to the request.
- SCOPE_EXIT({ request->Close(); });
+ SCOPE_EXIT {
+ request->Close();
+ };
// Get the event to check whether the request is async.
if (KEvent* event = request->GetEvent(); event != nullptr) {
@@ -1236,7 +1240,9 @@ Result KServerSession::SendReply(uintptr_t server_message, uintptr_t server_buff
}
// Close reference to the request once we're done processing it.
- SCOPE_EXIT({ request->Close(); });
+ SCOPE_EXIT {
+ request->Close();
+ };
// Extract relevant information from the request.
const uint64_t client_message = request->GetAddress();
@@ -1394,7 +1400,9 @@ void KServerSession::CleanupRequests() {
}
// Close a reference to the request once it's cleaned up.
- SCOPE_EXIT({ request->Close(); });
+ SCOPE_EXIT {
+ request->Close();
+ };
// Extract relevant information from the request.
const uint64_t client_message = request->GetAddress();
@@ -1491,7 +1499,9 @@ void KServerSession::OnClientClosed() {
ASSERT(thread != nullptr);
// Ensure that we close the request when done.
- SCOPE_EXIT({ request->Close(); });
+ SCOPE_EXIT {
+ request->Close();
+ };
// If we're terminating, close a reference to the thread and event.
if (terminate) {
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index f13e232b2..e928cfebc 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -66,6 +66,7 @@ enum class SuspendType : u32 {
Debug = 2,
Backtrace = 3,
Init = 4,
+ System = 5,
Count,
};
@@ -84,8 +85,9 @@ enum class ThreadState : u16 {
DebugSuspended = (1 << (2 + SuspendShift)),
BacktraceSuspended = (1 << (3 + SuspendShift)),
InitSuspended = (1 << (4 + SuspendShift)),
+ SystemSuspended = (1 << (5 + SuspendShift)),
- SuspendFlagMask = ((1 << 5) - 1) << SuspendShift,
+ SuspendFlagMask = ((1 << 6) - 1) << SuspendShift,
};
DECLARE_ENUM_FLAG_OPERATORS(ThreadState);
diff --git a/src/core/hle/kernel/k_thread_local_page.cpp b/src/core/hle/kernel/k_thread_local_page.cpp
index a632d1634..1952c0083 100644
--- a/src/core/hle/kernel/k_thread_local_page.cpp
+++ b/src/core/hle/kernel/k_thread_local_page.cpp
@@ -21,7 +21,9 @@ Result KThreadLocalPage::Initialize(KernelCore& kernel, KProcess* process) {
// Allocate a new page.
KPageBuffer* page_buf = KPageBuffer::Allocate(kernel);
R_UNLESS(page_buf != nullptr, ResultOutOfMemory);
- auto page_buf_guard = SCOPE_GUARD({ KPageBuffer::Free(kernel, page_buf); });
+ auto page_buf_guard = SCOPE_GUARD {
+ KPageBuffer::Free(kernel, page_buf);
+ };
// Map the address in.
const auto phys_addr = kernel.System().DeviceMemory().GetPhysicalAddr(page_buf);
diff --git a/src/core/hle/kernel/k_transfer_memory.cpp b/src/core/hle/kernel/k_transfer_memory.cpp
index cbb1b02bb..09295e8ad 100644
--- a/src/core/hle/kernel/k_transfer_memory.cpp
+++ b/src/core/hle/kernel/k_transfer_memory.cpp
@@ -24,7 +24,9 @@ Result KTransferMemory::Initialize(KProcessAddress addr, std::size_t size,
// Construct the page group, guarding to make sure our state is valid on exit.
m_page_group.emplace(m_kernel, page_table.GetBlockInfoManager());
- auto pg_guard = SCOPE_GUARD({ m_page_group.reset(); });
+ auto pg_guard = SCOPE_GUARD {
+ m_page_group.reset();
+ };
// Lock the memory.
R_TRY(page_table.LockForTransferMemory(std::addressof(*m_page_group), addr, size,
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 34b25be66..9e5eaeec4 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -109,7 +109,9 @@ struct KernelCore::Impl {
void Shutdown() {
is_shutting_down.store(true, std::memory_order_relaxed);
- SCOPE_EXIT({ is_shutting_down.store(false, std::memory_order_relaxed); });
+ SCOPE_EXIT {
+ is_shutting_down.store(false, std::memory_order_relaxed);
+ };
CloseServices();
@@ -1080,7 +1082,9 @@ std::jthread KernelCore::RunOnHostCoreProcess(std::string&& process_name,
process->Initialize(Svc::CreateProcessParameter{}, GetSystemResourceLimit(), false)));
// Ensure that we don't hold onto any extra references.
- SCOPE_EXIT({ process->Close(); });
+ SCOPE_EXIT {
+ process->Close();
+ };
// Register the new process.
KProcess::Register(*this, process);
@@ -1108,7 +1112,9 @@ void KernelCore::RunOnGuestCoreProcess(std::string&& process_name, std::function
process->Initialize(Svc::CreateProcessParameter{}, GetSystemResourceLimit(), false)));
// Ensure that we don't hold onto any extra references.
- SCOPE_EXIT({ process->Close(); });
+ SCOPE_EXIT {
+ process->Close();
+ };
// Register the new process.
KProcess::Register(*this, process);
@@ -1204,39 +1210,48 @@ const Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() const {
return *impl->hidbus_shared_mem;
}
-void KernelCore::SuspendApplication(bool suspended) {
+void KernelCore::SuspendEmulation(bool suspended) {
const bool should_suspend{exception_exited || suspended};
- const auto activity =
- should_suspend ? Svc::ProcessActivity::Paused : Svc::ProcessActivity::Runnable;
+ auto processes = GetProcessList();
- // Get the application process.
- KScopedAutoObject<KProcess> process = ApplicationProcess();
- if (process.IsNull()) {
- return;
+ for (auto& process : processes) {
+ KScopedLightLock ll{process->GetListLock()};
+
+ for (auto& thread : process->GetThreadList()) {
+ if (should_suspend) {
+ thread.RequestSuspend(SuspendType::System);
+ } else {
+ thread.Resume(SuspendType::System);
+ }
+ }
}
- // Set the new activity.
- process->SetActivity(activity);
+ if (!should_suspend) {
+ return;
+ }
// Wait for process execution to stop.
- bool must_wait{should_suspend};
-
- // KernelCore::SuspendApplication must be called from locked context,
- // or we could race another call to SetActivity, interfering with waiting.
- while (must_wait) {
+ // KernelCore::SuspendEmulation must be called from locked context,
+ // or we could race another call, interfering with waiting.
+ const auto TryWait = [&]() {
KScopedSchedulerLock sl{*this};
- // Assume that all threads have finished running.
- must_wait = false;
-
- for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) {
- if (Scheduler(i).GetSchedulerCurrentThread()->GetOwnerProcess() ==
- process.GetPointerUnsafe()) {
- // A thread has not finished running yet.
- // Continue waiting.
- must_wait = true;
+ for (auto& process : processes) {
+ for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) {
+ if (Scheduler(i).GetSchedulerCurrentThread()->GetOwnerProcess() ==
+ process.GetPointerUnsafe()) {
+ // A thread has not finished running yet.
+ // Continue waiting.
+ return false;
+ }
}
}
+
+ return true;
+ };
+
+ while (!TryWait()) {
+ // ...
}
}
@@ -1260,7 +1275,7 @@ bool KernelCore::IsShuttingDown() const {
void KernelCore::ExceptionalExitApplication() {
exception_exited = true;
- SuspendApplication(true);
+ SuspendEmulation(true);
}
void KernelCore::EnterSVCProfile() {
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 8ea5bed1c..57182c0c8 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -258,8 +258,8 @@ public:
/// Gets the shared memory object for HIDBus services.
const Kernel::KSharedMemory& GetHidBusSharedMem() const;
- /// Suspend/unsuspend application process.
- void SuspendApplication(bool suspend);
+ /// Suspend/unsuspend emulated processes.
+ void SuspendEmulation(bool suspend);
/// Exceptional exit application process.
void ExceptionalExitApplication();
diff --git a/src/core/hle/kernel/svc/svc_code_memory.cpp b/src/core/hle/kernel/svc/svc_code_memory.cpp
index bae4cb0cd..7be2802f0 100644
--- a/src/core/hle/kernel/svc/svc_code_memory.cpp
+++ b/src/core/hle/kernel/svc/svc_code_memory.cpp
@@ -45,7 +45,9 @@ Result CreateCodeMemory(Core::System& system, Handle* out, u64 address, uint64_t
KCodeMemory* code_mem = KCodeMemory::Create(kernel);
R_UNLESS(code_mem != nullptr, ResultOutOfResource);
- SCOPE_EXIT({ code_mem->Close(); });
+ SCOPE_EXIT {
+ code_mem->Close();
+ };
// Verify that the region is in range.
R_UNLESS(GetCurrentProcess(system.Kernel()).GetPageTable().Contains(address, size),
diff --git a/src/core/hle/kernel/svc/svc_device_address_space.cpp b/src/core/hle/kernel/svc/svc_device_address_space.cpp
index 42add9473..ac828320f 100644
--- a/src/core/hle/kernel/svc/svc_device_address_space.cpp
+++ b/src/core/hle/kernel/svc/svc_device_address_space.cpp
@@ -28,7 +28,9 @@ Result CreateDeviceAddressSpace(Core::System& system, Handle* out, uint64_t das_
// Create the device address space.
KDeviceAddressSpace* das = KDeviceAddressSpace::Create(system.Kernel());
R_UNLESS(das != nullptr, ResultOutOfResource);
- SCOPE_EXIT({ das->Close(); });
+ SCOPE_EXIT {
+ das->Close();
+ };
// Initialize the device address space.
R_TRY(das->Initialize(das_address, das_size));
diff --git a/src/core/hle/kernel/svc/svc_event.cpp b/src/core/hle/kernel/svc/svc_event.cpp
index 901202e6a..8e4beb396 100644
--- a/src/core/hle/kernel/svc/svc_event.cpp
+++ b/src/core/hle/kernel/svc/svc_event.cpp
@@ -72,10 +72,10 @@ Result CreateEvent(Core::System& system, Handle* out_write, Handle* out_read) {
event_reservation.Commit();
// Ensure that we clean up the event (and its only references are handle table) on function end.
- SCOPE_EXIT({
+ SCOPE_EXIT {
event->GetReadableEvent().Close();
event->Close();
- });
+ };
// Register the event.
KEvent::Register(kernel, event);
diff --git a/src/core/hle/kernel/svc/svc_ipc.cpp b/src/core/hle/kernel/svc/svc_ipc.cpp
index 85cc4f561..b619bd70a 100644
--- a/src/core/hle/kernel/svc/svc_ipc.cpp
+++ b/src/core/hle/kernel/svc/svc_ipc.cpp
@@ -129,11 +129,11 @@ Result ReplyAndReceiveImpl(KernelCore& kernel, int32_t* out_index, uintptr_t mes
}
// Ensure handles are closed when we're done.
- SCOPE_EXIT({
+ SCOPE_EXIT {
for (auto i = 0; i < num_handles; ++i) {
objs[i]->Close();
}
- });
+ };
R_RETURN(ReplyAndReceiveImpl(kernel, out_index, message, buffer_size, message_paddr, objs,
num_handles, reply_target, timeout_ns));
@@ -208,10 +208,10 @@ Result SendAsyncRequestWithUserBuffer(Core::System& system, Handle* out_event_ha
event_reservation.Commit();
// At end of scope, kill the standing references to the sub events.
- SCOPE_EXIT({
+ SCOPE_EXIT {
event->GetReadableEvent().Close();
event->Close();
- });
+ };
// Register the event.
KEvent::Register(system.Kernel(), event);
diff --git a/src/core/hle/kernel/svc/svc_port.cpp b/src/core/hle/kernel/svc/svc_port.cpp
index 737749f7d..9a22dadaf 100644
--- a/src/core/hle/kernel/svc/svc_port.cpp
+++ b/src/core/hle/kernel/svc/svc_port.cpp
@@ -68,10 +68,10 @@ Result CreatePort(Core::System& system, Handle* out_server, Handle* out_client,
port->Initialize(max_sessions, is_light, name);
// Ensure that we clean up the port (and its only references are handle table) on function end.
- SCOPE_EXIT({
+ SCOPE_EXIT {
port->GetServerPort().Close();
port->GetClientPort().Close();
- });
+ };
// Register the port.
KPort::Register(kernel, port);
@@ -150,10 +150,10 @@ Result ManageNamedPort(Core::System& system, Handle* out_server_handle, uint64_t
KPort::Register(system.Kernel(), port);
// Ensure that our only reference to the port is in the handle table when we're done.
- SCOPE_EXIT({
+ SCOPE_EXIT {
port->GetClientPort().Close();
port->GetServerPort().Close();
- });
+ };
// Register the handle in the table.
R_TRY(handle_table.Add(out_server_handle, std::addressof(port->GetServerPort())));
diff --git a/src/core/hle/kernel/svc/svc_resource_limit.cpp b/src/core/hle/kernel/svc/svc_resource_limit.cpp
index c8e820b6a..6f3972482 100644
--- a/src/core/hle/kernel/svc/svc_resource_limit.cpp
+++ b/src/core/hle/kernel/svc/svc_resource_limit.cpp
@@ -18,7 +18,9 @@ Result CreateResourceLimit(Core::System& system, Handle* out_handle) {
R_UNLESS(resource_limit != nullptr, ResultOutOfResource);
// Ensure we don't leak a reference to the limit.
- SCOPE_EXIT({ resource_limit->Close(); });
+ SCOPE_EXIT {
+ resource_limit->Close();
+ };
// Initialize the resource limit.
resource_limit->Initialize();
diff --git a/src/core/hle/kernel/svc/svc_session.cpp b/src/core/hle/kernel/svc/svc_session.cpp
index 2f5905f32..b034d21d1 100644
--- a/src/core/hle/kernel/svc/svc_session.cpp
+++ b/src/core/hle/kernel/svc/svc_session.cpp
@@ -69,10 +69,10 @@ Result CreateSession(Core::System& system, Handle* out_server, Handle* out_clien
// Ensure that we clean up the session (and its only references are handle table) on function
// end.
- SCOPE_EXIT({
+ SCOPE_EXIT {
session->GetClientSession().Close();
session->GetServerSession().Close();
- });
+ };
// Register the session.
T::Register(system.Kernel(), session);
diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp
index 6c79cfd8d..fb03908d7 100644
--- a/src/core/hle/kernel/svc/svc_synchronization.cpp
+++ b/src/core/hle/kernel/svc/svc_synchronization.cpp
@@ -78,11 +78,11 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha
}
// Ensure handles are closed when we're done.
- SCOPE_EXIT({
+ SCOPE_EXIT {
for (auto i = 0; i < num_handles; ++i) {
objs[i]->Close();
}
- });
+ };
// Convert the timeout from nanoseconds to ticks.
s64 timeout;
diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp
index 7681afa33..7517bb9d3 100644
--- a/src/core/hle/kernel/svc/svc_thread.cpp
+++ b/src/core/hle/kernel/svc/svc_thread.cpp
@@ -51,7 +51,9 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u
// Create the thread.
KThread* thread = KThread::Create(kernel);
R_UNLESS(thread != nullptr, ResultOutOfResource)
- SCOPE_EXIT({ thread->Close(); });
+ SCOPE_EXIT {
+ thread->Close();
+ };
// Initialize the thread.
{
diff --git a/src/core/hle/kernel/svc/svc_transfer_memory.cpp b/src/core/hle/kernel/svc/svc_transfer_memory.cpp
index 671bca23f..2ea0d4421 100644
--- a/src/core/hle/kernel/svc/svc_transfer_memory.cpp
+++ b/src/core/hle/kernel/svc/svc_transfer_memory.cpp
@@ -52,7 +52,9 @@ Result CreateTransferMemory(Core::System& system, Handle* out, u64 address, u64
R_UNLESS(trmem != nullptr, ResultOutOfResource);
// Ensure the only reference is in the handle table when we're done.
- SCOPE_EXIT({ trmem->Close(); });
+ SCOPE_EXIT {
+ trmem->Close();
+ };
// Ensure that the region is in range.
R_UNLESS(process.GetPageTable().Contains(address, size), ResultInvalidCurrentMemory);
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index 29a10ad13..ee9795532 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -329,9 +329,8 @@ bool ProfileManager::GetProfileBaseAndData(const ProfileInfo& user, ProfileBase&
/// Returns if the system is allowing user registrations or not
bool ProfileManager::CanSystemRegisterUser() const {
- return false; // TODO(ogniK): Games shouldn't have
- // access to user registration, when we
- // emulate qlaunch. Update this to dynamically change.
+ // TODO: Both games and applets can register users. Determine when this condition is not meet.
+ return true;
}
bool ProfileManager::RemoveUser(UUID uuid) {
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 9dc710ba9..8c4e14f08 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -8,13 +8,13 @@
namespace Service::AM {
-void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) {
+void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
- server_manager->RegisterNamedService(
- "appletAE", std::make_shared<IAllSystemAppletProxiesService>(system, nvnflinger));
- server_manager->RegisterNamedService(
- "appletOE", std::make_shared<IApplicationProxyService>(system, nvnflinger));
+ server_manager->RegisterNamedService("appletAE",
+ std::make_shared<IAllSystemAppletProxiesService>(system));
+ server_manager->RegisterNamedService("appletOE",
+ std::make_shared<IApplicationProxyService>(system));
ServerManager::RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 4a2d797bd..1afe253ae 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -7,12 +7,8 @@ namespace Core {
class System;
}
-namespace Service::Nvnflinger {
-class Nvnflinger;
-}
-
namespace Service::AM {
-void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system);
+void LoopProcess(Core::System& system);
} // namespace Service::AM
diff --git a/src/core/hle/service/am/applet.h b/src/core/hle/service/am/applet.h
index 4f34d4811..ad602153e 100644
--- a/src/core/hle/service/am/applet.h
+++ b/src/core/hle/service/am/applet.h
@@ -14,10 +14,9 @@
#include "core/hle/service/am/am_types.h"
#include "core/hle/service/am/applet_message_queue.h"
+#include "core/hle/service/am/display_layer_manager.h"
#include "core/hle/service/am/hid_registration.h"
-#include "core/hle/service/am/managed_layer_holder.h"
#include "core/hle/service/am/process.h"
-#include "core/hle/service/am/system_buffer_manager.h"
namespace Service::AM {
@@ -54,8 +53,7 @@ struct Applet {
HidRegistration hid_registration;
// vi state
- SystemBufferManager system_buffer_manager{};
- ManagedLayerHolder managed_layer_holder{};
+ DisplayLayerManager display_layer_manager{};
// Applet common functions
Result terminate_result{};
diff --git a/src/core/hle/service/am/applet_data_broker.cpp b/src/core/hle/service/am/applet_data_broker.cpp
index 4d58c4db5..9057244a9 100644
--- a/src/core/hle/service/am/applet_data_broker.cpp
+++ b/src/core/hle/service/am/applet_data_broker.cpp
@@ -24,11 +24,11 @@ void AppletStorageChannel::Push(std::shared_ptr<IStorage> storage) {
Result AppletStorageChannel::Pop(std::shared_ptr<IStorage>* out_storage) {
std::scoped_lock lk{m_lock};
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (m_data.empty()) {
m_event.Clear();
}
- });
+ };
R_UNLESS(!m_data.empty(), AM::ResultNoDataInChannel);
diff --git a/src/core/hle/service/am/applet_manager.cpp b/src/core/hle/service/am/applet_manager.cpp
index 4c7266f89..2e109181d 100644
--- a/src/core/hle/service/am/applet_manager.cpp
+++ b/src/core/hle/service/am/applet_manager.cpp
@@ -35,6 +35,21 @@ AppletStorageChannel& InitializeFakeCallerApplet(Core::System& system,
return applet->caller_applet_broker->GetInData();
}
+void PushInShowQlaunch(Core::System& system, AppletStorageChannel& channel) {
+ const CommonArguments arguments{
+ .arguments_version = CommonArgumentVersion::Version3,
+ .size = CommonArgumentSize::Version3,
+ .library_version = 0,
+ .theme_color = ThemeColor::BasicBlack,
+ .play_startup_sound = true,
+ .system_tick = system.CoreTiming().GetClockTicks(),
+ };
+
+ std::vector<u8> argument_data(sizeof(arguments));
+ std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
+ channel.Push(std::make_shared<IStorage>(system, std::move(argument_data)));
+}
+
void PushInShowAlbum(Core::System& system, AppletStorageChannel& channel) {
const CommonArguments arguments{
.arguments_version = CommonArgumentVersion::Version3,
@@ -284,6 +299,9 @@ void AppletManager::CreateAndInsertByFrontendAppletParameters(
// Starting from frontend, some applets require input data.
switch (applet->applet_id) {
+ case AppletId::QLaunch:
+ PushInShowQlaunch(m_system, InitializeFakeCallerApplet(m_system, applet));
+ break;
case AppletId::Cabinet:
PushInShowCabinetData(m_system, InitializeFakeCallerApplet(m_system, applet));
break;
diff --git a/src/core/hle/service/am/display_layer_manager.cpp b/src/core/hle/service/am/display_layer_manager.cpp
new file mode 100644
index 000000000..85ff6fb88
--- /dev/null
+++ b/src/core/hle/service/am/display_layer_manager.cpp
@@ -0,0 +1,151 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/core.h"
+#include "core/hle/service/am/display_layer_manager.h"
+#include "core/hle/service/sm/sm.h"
+#include "core/hle/service/vi/application_display_service.h"
+#include "core/hle/service/vi/container.h"
+#include "core/hle/service/vi/manager_display_service.h"
+#include "core/hle/service/vi/manager_root_service.h"
+#include "core/hle/service/vi/shared_buffer_manager.h"
+#include "core/hle/service/vi/vi_results.h"
+#include "core/hle/service/vi/vi_types.h"
+
+namespace Service::AM {
+
+DisplayLayerManager::DisplayLayerManager() = default;
+DisplayLayerManager::~DisplayLayerManager() {
+ this->Finalize();
+}
+
+void DisplayLayerManager::Initialize(Core::System& system, Kernel::KProcess* process,
+ AppletId applet_id, LibraryAppletMode mode) {
+ R_ASSERT(system.ServiceManager()
+ .GetService<VI::IManagerRootService>("vi:m", true)
+ ->GetDisplayService(&m_display_service, VI::Policy::Compositor));
+ R_ASSERT(m_display_service->GetManagerDisplayService(&m_manager_display_service));
+
+ m_process = process;
+ m_system_shared_buffer_id = 0;
+ m_system_shared_layer_id = 0;
+ m_applet_id = applet_id;
+ m_buffer_sharing_enabled = false;
+ m_blending_enabled = mode == LibraryAppletMode::PartialForeground ||
+ mode == LibraryAppletMode::PartialForegroundIndirectDisplay;
+}
+
+void DisplayLayerManager::Finalize() {
+ if (!m_manager_display_service) {
+ return;
+ }
+
+ // Clean up managed layers.
+ for (const auto& layer : m_managed_display_layers) {
+ m_manager_display_service->DestroyManagedLayer(layer);
+ }
+
+ for (const auto& layer : m_managed_display_recording_layers) {
+ m_manager_display_service->DestroyManagedLayer(layer);
+ }
+
+ // Clean up shared layers.
+ if (m_buffer_sharing_enabled) {
+ m_manager_display_service->DestroySharedLayerSession(m_process);
+ }
+
+ m_manager_display_service = nullptr;
+ m_display_service = nullptr;
+}
+
+Result DisplayLayerManager::CreateManagedDisplayLayer(u64* out_layer_id) {
+ R_UNLESS(m_manager_display_service != nullptr, VI::ResultOperationFailed);
+
+ // TODO(Subv): Find out how AM determines the display to use, for now just
+ // create the layer in the Default display.
+ u64 display_id;
+ R_TRY(m_display_service->OpenDisplay(&display_id, VI::DisplayName{"Default"}));
+ R_TRY(m_manager_display_service->CreateManagedLayer(
+ out_layer_id, 0, display_id, Service::AppletResourceUserId{m_process->GetProcessId()}));
+
+ m_manager_display_service->SetLayerVisibility(m_visible, *out_layer_id);
+ m_managed_display_layers.emplace(*out_layer_id);
+
+ R_SUCCEED();
+}
+
+Result DisplayLayerManager::CreateManagedDisplaySeparableLayer(u64* out_layer_id,
+ u64* out_recording_layer_id) {
+ R_UNLESS(m_manager_display_service != nullptr, VI::ResultOperationFailed);
+
+ // TODO(Subv): Find out how AM determines the display to use, for now just
+ // create the layer in the Default display.
+ // This calls nn::vi::CreateRecordingLayer() which creates another layer.
+ // Currently we do not support more than 1 layer per display, output 1 layer id for now.
+ // Outputting 1 layer id instead of the expected 2 has not been observed to cause any adverse
+ // side effects.
+ *out_recording_layer_id = 0;
+ R_RETURN(this->CreateManagedDisplayLayer(out_layer_id));
+}
+
+Result DisplayLayerManager::IsSystemBufferSharingEnabled() {
+ // Succeed if already enabled.
+ R_SUCCEED_IF(m_buffer_sharing_enabled);
+
+ // Ensure we can access shared layers.
+ R_UNLESS(m_manager_display_service != nullptr, VI::ResultOperationFailed);
+ R_UNLESS(m_applet_id != AppletId::Application, VI::ResultPermissionDenied);
+
+ // Create the shared layer.
+ u64 display_id;
+ R_TRY(m_display_service->OpenDisplay(&display_id, VI::DisplayName{"Default"}));
+ R_TRY(m_manager_display_service->CreateSharedLayerSession(m_process, &m_system_shared_buffer_id,
+ &m_system_shared_layer_id, display_id,
+ m_blending_enabled));
+
+ // We succeeded, so set up remaining state.
+ m_buffer_sharing_enabled = true;
+ m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id);
+ R_SUCCEED();
+}
+
+Result DisplayLayerManager::GetSystemSharedLayerHandle(u64* out_system_shared_buffer_id,
+ u64* out_system_shared_layer_id) {
+ R_TRY(this->IsSystemBufferSharingEnabled());
+
+ *out_system_shared_buffer_id = m_system_shared_buffer_id;
+ *out_system_shared_layer_id = m_system_shared_layer_id;
+
+ R_SUCCEED();
+}
+
+void DisplayLayerManager::SetWindowVisibility(bool visible) {
+ if (m_visible == visible) {
+ return;
+ }
+
+ m_visible = visible;
+
+ if (m_manager_display_service) {
+ if (m_system_shared_layer_id) {
+ m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id);
+ }
+
+ for (const auto layer_id : m_managed_display_layers) {
+ m_manager_display_service->SetLayerVisibility(m_visible, layer_id);
+ }
+ }
+}
+
+bool DisplayLayerManager::GetWindowVisibility() const {
+ return m_visible;
+}
+
+Result DisplayLayerManager::WriteAppletCaptureBuffer(bool* out_was_written,
+ s32* out_fbshare_layer_index) {
+ R_UNLESS(m_buffer_sharing_enabled, VI::ResultPermissionDenied);
+ R_RETURN(m_display_service->GetContainer()->GetSharedBufferManager()->WriteAppletCaptureBuffer(
+ out_was_written, out_fbshare_layer_index));
+}
+
+} // namespace Service::AM
diff --git a/src/core/hle/service/am/display_layer_manager.h b/src/core/hle/service/am/display_layer_manager.h
new file mode 100644
index 000000000..a66509c04
--- /dev/null
+++ b/src/core/hle/service/am/display_layer_manager.h
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <set>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "core/hle/service/am/am_types.h"
+
+namespace Core {
+class System;
+}
+
+namespace Kernel {
+class KProcess;
+}
+
+namespace Service::VI {
+class IApplicationDisplayService;
+class IManagerDisplayService;
+} // namespace Service::VI
+
+namespace Service::AM {
+
+class DisplayLayerManager {
+public:
+ explicit DisplayLayerManager();
+ ~DisplayLayerManager();
+
+ void Initialize(Core::System& system, Kernel::KProcess* process, AppletId applet_id,
+ LibraryAppletMode mode);
+ void Finalize();
+
+ Result CreateManagedDisplayLayer(u64* out_layer_id);
+ Result CreateManagedDisplaySeparableLayer(u64* out_layer_id, u64* out_recording_layer_id);
+
+ Result IsSystemBufferSharingEnabled();
+ Result GetSystemSharedLayerHandle(u64* out_system_shared_buffer_id,
+ u64* out_system_shared_layer_id);
+
+ void SetWindowVisibility(bool visible);
+ bool GetWindowVisibility() const;
+
+ Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_fbshare_layer_index);
+
+private:
+ Kernel::KProcess* m_process{};
+ std::shared_ptr<VI::IApplicationDisplayService> m_display_service{};
+ std::shared_ptr<VI::IManagerDisplayService> m_manager_display_service{};
+ std::set<u64> m_managed_display_layers{};
+ std::set<u64> m_managed_display_recording_layers{};
+ u64 m_system_shared_buffer_id{};
+ u64 m_system_shared_layer_id{};
+ AppletId m_applet_id{};
+ bool m_buffer_sharing_enabled{};
+ bool m_blending_enabled{};
+ bool m_visible{true};
+};
+
+} // namespace Service::AM
diff --git a/src/core/hle/service/am/frontend/applet_web_browser.cpp b/src/core/hle/service/am/frontend/applet_web_browser.cpp
index bb60260b4..835c20c4e 100644
--- a/src/core/hle/service/am/frontend/applet_web_browser.cpp
+++ b/src/core/hle/service/am/frontend/applet_web_browser.cpp
@@ -22,7 +22,7 @@
#include "core/hle/service/am/frontend/applet_web_browser.h"
#include "core/hle/service/am/service/storage.h"
#include "core/hle/service/filesystem/filesystem.h"
-#include "core/hle/service/ns/iplatform_service_manager.h"
+#include "core/hle/service/ns/platform_service_manager.h"
#include "core/loader/loader.h"
namespace Service::AM::Frontend {
diff --git a/src/core/hle/service/am/library_applet_storage.cpp b/src/core/hle/service/am/library_applet_storage.cpp
index 46e6c0111..0412c215d 100644
--- a/src/core/hle/service/am/library_applet_storage.cpp
+++ b/src/core/hle/service/am/library_applet_storage.cpp
@@ -70,7 +70,7 @@ public:
Result Read(s64 offset, void* buffer, size_t size) override {
R_TRY(ValidateOffset(offset, size, m_size));
- m_memory.ReadBlock(m_trmem->GetSourceAddress(), buffer, size);
+ m_memory.ReadBlock(m_trmem->GetSourceAddress() + offset, buffer, size);
R_SUCCEED();
}
@@ -79,7 +79,7 @@ public:
R_UNLESS(m_is_writable, ResultUnknown);
R_TRY(ValidateOffset(offset, size, m_size));
- m_memory.WriteBlock(m_trmem->GetSourceAddress(), buffer, size);
+ m_memory.WriteBlock(m_trmem->GetSourceAddress() + offset, buffer, size);
R_SUCCEED();
}
diff --git a/src/core/hle/service/am/managed_layer_holder.cpp b/src/core/hle/service/am/managed_layer_holder.cpp
deleted file mode 100644
index 61eb8641a..000000000
--- a/src/core/hle/service/am/managed_layer_holder.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "core/hle/service/am/managed_layer_holder.h"
-#include "core/hle/service/nvnflinger/nvnflinger.h"
-
-namespace Service::AM {
-
-ManagedLayerHolder::ManagedLayerHolder() = default;
-ManagedLayerHolder::~ManagedLayerHolder() {
- if (!m_nvnflinger) {
- return;
- }
-
- for (const auto& layer : m_managed_display_layers) {
- m_nvnflinger->DestroyLayer(layer);
- }
-
- for (const auto& layer : m_managed_display_recording_layers) {
- m_nvnflinger->DestroyLayer(layer);
- }
-
- m_nvnflinger = nullptr;
-}
-
-void ManagedLayerHolder::Initialize(Nvnflinger::Nvnflinger* nvnflinger) {
- m_nvnflinger = nvnflinger;
-}
-
-void ManagedLayerHolder::CreateManagedDisplayLayer(u64* out_layer) {
- // TODO(Subv): Find out how AM determines the display to use, for now just
- // create the layer in the Default display.
- const auto display_id = m_nvnflinger->OpenDisplay("Default");
- const auto layer_id = m_nvnflinger->CreateLayer(*display_id);
-
- m_managed_display_layers.emplace(*layer_id);
-
- *out_layer = *layer_id;
-}
-
-void ManagedLayerHolder::CreateManagedDisplaySeparableLayer(u64* out_layer,
- u64* out_recording_layer) {
- // TODO(Subv): Find out how AM determines the display to use, for now just
- // create the layer in the Default display.
- // This calls nn::vi::CreateRecordingLayer() which creates another layer.
- // Currently we do not support more than 1 layer per display, output 1 layer id for now.
- // Outputting 1 layer id instead of the expected 2 has not been observed to cause any adverse
- // side effects.
- // TODO: Support multiple layers
- const auto display_id = m_nvnflinger->OpenDisplay("Default");
- const auto layer_id = m_nvnflinger->CreateLayer(*display_id);
-
- m_managed_display_layers.emplace(*layer_id);
-
- *out_layer = *layer_id;
- *out_recording_layer = 0;
-}
-
-} // namespace Service::AM
diff --git a/src/core/hle/service/am/managed_layer_holder.h b/src/core/hle/service/am/managed_layer_holder.h
deleted file mode 100644
index f7fe03f24..000000000
--- a/src/core/hle/service/am/managed_layer_holder.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <set>
-
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-
-namespace Service::Nvnflinger {
-class Nvnflinger;
-}
-
-namespace Service::AM {
-
-class ManagedLayerHolder {
-public:
- ManagedLayerHolder();
- ~ManagedLayerHolder();
-
- void Initialize(Nvnflinger::Nvnflinger* nvnflinger);
- void CreateManagedDisplayLayer(u64* out_layer);
- void CreateManagedDisplaySeparableLayer(u64* out_layer, u64* out_recording_layer);
-
-private:
- Nvnflinger::Nvnflinger* m_nvnflinger{};
- std::set<u64> m_managed_display_layers{};
- std::set<u64> m_managed_display_recording_layers{};
-};
-
-} // namespace Service::AM
diff --git a/src/core/hle/service/am/process.cpp b/src/core/hle/service/am/process.cpp
index 992c50713..388d2045c 100644
--- a/src/core/hle/service/am/process.cpp
+++ b/src/core/hle/service/am/process.cpp
@@ -68,7 +68,9 @@ bool Process::Initialize(u64 program_id, u8 minimum_key_generation, u8 maximum_k
Kernel::KProcess::Register(m_system.Kernel(), process);
// On exit, ensure we free the additional reference to the process.
- SCOPE_EXIT({ process->Close(); });
+ SCOPE_EXIT {
+ process->Close();
+ };
// Insert process modules into memory.
const auto [load_result, load_parameters] = app_loader->Load(*process, m_system);
diff --git a/src/core/hle/service/am/service/all_system_applet_proxies_service.cpp b/src/core/hle/service/am/service/all_system_applet_proxies_service.cpp
index eebd90ba2..21747783a 100644
--- a/src/core/hle/service/am/service/all_system_applet_proxies_service.cpp
+++ b/src/core/hle/service/am/service/all_system_applet_proxies_service.cpp
@@ -10,9 +10,8 @@
namespace Service::AM {
-IAllSystemAppletProxiesService::IAllSystemAppletProxiesService(Core::System& system_,
- Nvnflinger::Nvnflinger& nvnflinger)
- : ServiceFramework{system_, "appletAE"}, m_nvnflinger{nvnflinger} {
+IAllSystemAppletProxiesService::IAllSystemAppletProxiesService(Core::System& system_)
+ : ServiceFramework{system_, "appletAE"} {
// clang-format off
static const FunctionInfo functions[] = {
{100, D<&IAllSystemAppletProxiesService::OpenSystemAppletProxy>, "OpenSystemAppletProxy"},
@@ -37,8 +36,8 @@ Result IAllSystemAppletProxiesService::OpenSystemAppletProxy(
LOG_DEBUG(Service_AM, "called");
if (const auto applet = this->GetAppletFromProcessId(pid); applet) {
- *out_system_applet_proxy = std::make_shared<ISystemAppletProxy>(
- system, applet, process_handle.Get(), m_nvnflinger);
+ *out_system_applet_proxy =
+ std::make_shared<ISystemAppletProxy>(system, applet, process_handle.Get());
R_SUCCEED();
} else {
UNIMPLEMENTED();
@@ -53,8 +52,8 @@ Result IAllSystemAppletProxiesService::OpenLibraryAppletProxy(
LOG_DEBUG(Service_AM, "called");
if (const auto applet = this->GetAppletFromProcessId(pid); applet) {
- *out_library_applet_proxy = std::make_shared<ILibraryAppletProxy>(
- system, applet, process_handle.Get(), m_nvnflinger);
+ *out_library_applet_proxy =
+ std::make_shared<ILibraryAppletProxy>(system, applet, process_handle.Get());
R_SUCCEED();
} else {
UNIMPLEMENTED();
diff --git a/src/core/hle/service/am/service/all_system_applet_proxies_service.h b/src/core/hle/service/am/service/all_system_applet_proxies_service.h
index 38b1ca2ea..0e2dcb86d 100644
--- a/src/core/hle/service/am/service/all_system_applet_proxies_service.h
+++ b/src/core/hle/service/am/service/all_system_applet_proxies_service.h
@@ -8,10 +8,6 @@
namespace Service {
-namespace Nvnflinger {
-class Nvnflinger;
-}
-
namespace AM {
struct Applet;
@@ -22,8 +18,7 @@ class ISystemAppletProxy;
class IAllSystemAppletProxiesService final
: public ServiceFramework<IAllSystemAppletProxiesService> {
public:
- explicit IAllSystemAppletProxiesService(Core::System& system_,
- Nvnflinger::Nvnflinger& nvnflinger);
+ explicit IAllSystemAppletProxiesService(Core::System& system_);
~IAllSystemAppletProxiesService() override;
private:
@@ -40,7 +35,6 @@ private:
private:
std::shared_ptr<Applet> GetAppletFromProcessId(ProcessId pid);
- Nvnflinger::Nvnflinger& m_nvnflinger;
};
} // namespace AM
diff --git a/src/core/hle/service/am/service/application_functions.cpp b/src/core/hle/service/am/service/application_functions.cpp
index b788fddd4..cb53b07e0 100644
--- a/src/core/hle/service/am/service/application_functions.cpp
+++ b/src/core/hle/service/am/service/application_functions.cpp
@@ -15,7 +15,9 @@
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/filesystem/save_data_controller.h"
-#include "core/hle/service/ns/ns.h"
+#include "core/hle/service/glue/glue_manager.h"
+#include "core/hle/service/ns/application_manager_interface.h"
+#include "core/hle/service/ns/service_getter_interface.h"
#include "core/hle/service/sm/sm.h"
namespace Service::AM {
@@ -40,7 +42,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_
{26, D<&IApplicationFunctions::GetSaveDataSize>, "GetSaveDataSize"},
{27, D<&IApplicationFunctions::CreateCacheStorage>, "CreateCacheStorage"},
{28, D<&IApplicationFunctions::GetSaveDataSizeMax>, "GetSaveDataSizeMax"},
- {29, nullptr, "GetCacheStorageMax"},
+ {29, D<&IApplicationFunctions::GetCacheStorageMax>, "GetCacheStorageMax"},
{30, D<&IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed>, "BeginBlockingHomeButtonShortAndLongPressed"},
{31, D<&IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed>, "EndBlockingHomeButtonShortAndLongPressed"},
{32, D<&IApplicationFunctions::BeginBlockingHomeButton>, "BeginBlockingHomeButton"},
@@ -162,11 +164,13 @@ Result IApplicationFunctions::GetDesiredLanguage(Out<u64> out_language_code) {
// Call IApplicationManagerInterface implementation.
auto& service_manager = system.ServiceManager();
- auto ns_am2 = service_manager.GetService<NS::NS>("ns:am2");
- auto app_man = ns_am2->GetApplicationManagerInterface();
+ auto ns_am2 = service_manager.GetService<NS::IServiceGetterInterface>("ns:am2");
+
+ std::shared_ptr<NS::IApplicationManagerInterface> app_man;
+ R_TRY(ns_am2->GetApplicationManagerInterface(&app_man));
// Get desired application language
- u8 desired_language{};
+ NS::ApplicationLanguage desired_language{};
R_TRY(app_man->GetApplicationDesiredLanguage(&desired_language, supported_languages));
// Convert to settings language code.
@@ -267,6 +271,22 @@ Result IApplicationFunctions::GetSaveDataSizeMax(Out<u64> out_max_normal_size,
R_SUCCEED();
}
+Result IApplicationFunctions::GetCacheStorageMax(Out<u32> out_cache_storage_index_max,
+ Out<u64> out_max_journal_size) {
+ LOG_DEBUG(Service_AM, "called");
+
+ std::vector<u8> nacp;
+ R_TRY(system.GetARPManager().GetControlProperty(&nacp, m_applet->program_id));
+
+ auto raw_nacp = std::make_unique<FileSys::RawNACP>();
+ std::memcpy(raw_nacp.get(), nacp.data(), std::min(sizeof(*raw_nacp), nacp.size()));
+
+ *out_cache_storage_index_max = static_cast<u32>(raw_nacp->cache_storage_max_index);
+ *out_max_journal_size = static_cast<u64>(raw_nacp->cache_storage_data_and_journal_max_size);
+
+ R_SUCCEED();
+}
+
Result IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed(s64 unused) {
LOG_WARNING(Service_AM, "(STUBBED) called");
diff --git a/src/core/hle/service/am/service/application_functions.h b/src/core/hle/service/am/service/application_functions.h
index 3548202f8..10025a152 100644
--- a/src/core/hle/service/am/service/application_functions.h
+++ b/src/core/hle/service/am/service/application_functions.h
@@ -40,6 +40,7 @@ private:
Result CreateCacheStorage(Out<u32> out_target_media, Out<u64> out_required_size, u16 index,
u64 normal_size, u64 journal_size);
Result GetSaveDataSizeMax(Out<u64> out_max_normal_size, Out<u64> out_max_journal_size);
+ Result GetCacheStorageMax(Out<u32> out_cache_storage_index_max, Out<u64> out_max_journal_size);
Result BeginBlockingHomeButtonShortAndLongPressed(s64 unused);
Result EndBlockingHomeButtonShortAndLongPressed();
Result BeginBlockingHomeButton(s64 timeout_ns);
diff --git a/src/core/hle/service/am/service/application_proxy.cpp b/src/core/hle/service/am/service/application_proxy.cpp
index 776f4552b..19d6a3b89 100644
--- a/src/core/hle/service/am/service/application_proxy.cpp
+++ b/src/core/hle/service/am/service/application_proxy.cpp
@@ -17,9 +17,9 @@
namespace Service::AM {
IApplicationProxy::IApplicationProxy(Core::System& system_, std::shared_ptr<Applet> applet,
- Kernel::KProcess* process, Nvnflinger::Nvnflinger& nvnflinger)
- : ServiceFramework{system_, "IApplicationProxy"},
- m_nvnflinger{nvnflinger}, m_process{process}, m_applet{std::move(applet)} {
+ Kernel::KProcess* process)
+ : ServiceFramework{system_, "IApplicationProxy"}, m_process{process}, m_applet{
+ std::move(applet)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, D<&IApplicationProxy::GetCommonStateGetter>, "GetCommonStateGetter"},
@@ -77,8 +77,7 @@ Result IApplicationProxy::GetWindowController(
Result IApplicationProxy::GetSelfController(
Out<SharedPointer<ISelfController>> out_self_controller) {
LOG_DEBUG(Service_AM, "called");
- *out_self_controller =
- std::make_shared<ISelfController>(system, m_applet, m_process, m_nvnflinger);
+ *out_self_controller = std::make_shared<ISelfController>(system, m_applet, m_process);
R_SUCCEED();
}
diff --git a/src/core/hle/service/am/service/application_proxy.h b/src/core/hle/service/am/service/application_proxy.h
index 1ebc593ba..6da350df7 100644
--- a/src/core/hle/service/am/service/application_proxy.h
+++ b/src/core/hle/service/am/service/application_proxy.h
@@ -22,7 +22,7 @@ class IWindowController;
class IApplicationProxy final : public ServiceFramework<IApplicationProxy> {
public:
explicit IApplicationProxy(Core::System& system_, std::shared_ptr<Applet> applet,
- Kernel::KProcess* process, Nvnflinger::Nvnflinger& nvnflinger);
+ Kernel::KProcess* process);
~IApplicationProxy();
private:
@@ -40,7 +40,6 @@ private:
Out<SharedPointer<IApplicationFunctions>> out_application_functions);
private:
- Nvnflinger::Nvnflinger& m_nvnflinger;
Kernel::KProcess* const m_process;
const std::shared_ptr<Applet> m_applet;
};
diff --git a/src/core/hle/service/am/service/application_proxy_service.cpp b/src/core/hle/service/am/service/application_proxy_service.cpp
index 36d4478df..fd66e77b9 100644
--- a/src/core/hle/service/am/service/application_proxy_service.cpp
+++ b/src/core/hle/service/am/service/application_proxy_service.cpp
@@ -10,9 +10,8 @@
namespace Service::AM {
-IApplicationProxyService::IApplicationProxyService(Core::System& system_,
- Nvnflinger::Nvnflinger& nvnflinger)
- : ServiceFramework{system_, "appletOE"}, m_nvnflinger{nvnflinger} {
+IApplicationProxyService::IApplicationProxyService(Core::System& system_)
+ : ServiceFramework{system_, "appletOE"} {
static const FunctionInfo functions[] = {
{0, D<&IApplicationProxyService::OpenApplicationProxy>, "OpenApplicationProxy"},
};
@@ -28,7 +27,7 @@ Result IApplicationProxyService::OpenApplicationProxy(
if (const auto applet = this->GetAppletFromProcessId(pid)) {
*out_application_proxy =
- std::make_shared<IApplicationProxy>(system, applet, process_handle.Get(), m_nvnflinger);
+ std::make_shared<IApplicationProxy>(system, applet, process_handle.Get());
R_SUCCEED();
} else {
UNIMPLEMENTED();
diff --git a/src/core/hle/service/am/service/application_proxy_service.h b/src/core/hle/service/am/service/application_proxy_service.h
index 1c1d32d0b..8efafa31a 100644
--- a/src/core/hle/service/am/service/application_proxy_service.h
+++ b/src/core/hle/service/am/service/application_proxy_service.h
@@ -8,10 +8,6 @@
namespace Service {
-namespace Nvnflinger {
-class Nvnflinger;
-}
-
namespace AM {
struct Applet;
@@ -19,7 +15,7 @@ class IApplicationProxy;
class IApplicationProxyService final : public ServiceFramework<IApplicationProxyService> {
public:
- explicit IApplicationProxyService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger);
+ explicit IApplicationProxyService(Core::System& system_);
~IApplicationProxyService() override;
private:
@@ -28,7 +24,6 @@ private:
private:
std::shared_ptr<Applet> GetAppletFromProcessId(ProcessId pid);
- Nvnflinger::Nvnflinger& m_nvnflinger;
};
} // namespace AM
diff --git a/src/core/hle/service/am/service/display_controller.cpp b/src/core/hle/service/am/service/display_controller.cpp
index 249c73dfb..ed71f9093 100644
--- a/src/core/hle/service/am/service/display_controller.cpp
+++ b/src/core/hle/service/am/service/display_controller.cpp
@@ -69,7 +69,7 @@ Result IDisplayController::ClearCaptureBuffer(bool unknown0, s32 fbshare_layer_i
Result IDisplayController::AcquireLastForegroundCaptureSharedBuffer(
Out<bool> out_was_written, Out<s32> out_fbshare_layer_index) {
LOG_WARNING(Service_AM, "(STUBBED) called");
- R_RETURN(applet->system_buffer_manager.WriteAppletCaptureBuffer(out_was_written,
+ R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(out_was_written,
out_fbshare_layer_index));
}
@@ -81,7 +81,7 @@ Result IDisplayController::ReleaseLastForegroundCaptureSharedBuffer() {
Result IDisplayController::AcquireCallerAppletCaptureSharedBuffer(
Out<bool> out_was_written, Out<s32> out_fbshare_layer_index) {
LOG_WARNING(Service_AM, "(STUBBED) called");
- R_RETURN(applet->system_buffer_manager.WriteAppletCaptureBuffer(out_was_written,
+ R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(out_was_written,
out_fbshare_layer_index));
}
@@ -93,7 +93,7 @@ Result IDisplayController::ReleaseCallerAppletCaptureSharedBuffer() {
Result IDisplayController::AcquireLastApplicationCaptureSharedBuffer(
Out<bool> out_was_written, Out<s32> out_fbshare_layer_index) {
LOG_WARNING(Service_AM, "(STUBBED) called");
- R_RETURN(applet->system_buffer_manager.WriteAppletCaptureBuffer(out_was_written,
+ R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(out_was_written,
out_fbshare_layer_index));
}
diff --git a/src/core/hle/service/am/service/library_applet_creator.cpp b/src/core/hle/service/am/service/library_applet_creator.cpp
index 166637d60..c97358d81 100644
--- a/src/core/hle/service/am/service/library_applet_creator.cpp
+++ b/src/core/hle/service/am/service/library_applet_creator.cpp
@@ -135,7 +135,7 @@ std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
case LibraryAppletMode::AllForegroundInitiallyHidden:
applet->hid_registration.EnableAppletToGetInput(false);
applet->focus_state = FocusState::NotInFocus;
- applet->system_buffer_manager.SetWindowVisibility(false);
+ applet->display_layer_manager.SetWindowVisibility(false);
applet->message_queue.PushMessage(AppletMessage::ChangeIntoBackground);
break;
}
diff --git a/src/core/hle/service/am/service/library_applet_proxy.cpp b/src/core/hle/service/am/service/library_applet_proxy.cpp
index bcb44a71c..58e709347 100644
--- a/src/core/hle/service/am/service/library_applet_proxy.cpp
+++ b/src/core/hle/service/am/service/library_applet_proxy.cpp
@@ -19,10 +19,9 @@
namespace Service::AM {
ILibraryAppletProxy::ILibraryAppletProxy(Core::System& system_, std::shared_ptr<Applet> applet,
- Kernel::KProcess* process,
- Nvnflinger::Nvnflinger& nvnflinger)
- : ServiceFramework{system_, "ILibraryAppletProxy"},
- m_nvnflinger{nvnflinger}, m_process{process}, m_applet{std::move(applet)} {
+ Kernel::KProcess* process)
+ : ServiceFramework{system_, "ILibraryAppletProxy"}, m_process{process}, m_applet{
+ std::move(applet)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, D<&ILibraryAppletProxy::GetCommonStateGetter>, "GetCommonStateGetter"},
@@ -83,8 +82,7 @@ Result ILibraryAppletProxy::GetWindowController(
Result ILibraryAppletProxy::GetSelfController(
Out<SharedPointer<ISelfController>> out_self_controller) {
LOG_DEBUG(Service_AM, "called");
- *out_self_controller =
- std::make_shared<ISelfController>(system, m_applet, m_process, m_nvnflinger);
+ *out_self_controller = std::make_shared<ISelfController>(system, m_applet, m_process);
R_SUCCEED();
}
diff --git a/src/core/hle/service/am/service/library_applet_proxy.h b/src/core/hle/service/am/service/library_applet_proxy.h
index 23e64e295..7d0714b85 100644
--- a/src/core/hle/service/am/service/library_applet_proxy.h
+++ b/src/core/hle/service/am/service/library_applet_proxy.h
@@ -25,7 +25,7 @@ class IWindowController;
class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> {
public:
explicit ILibraryAppletProxy(Core::System& system_, std::shared_ptr<Applet> applet,
- Kernel::KProcess* process, Nvnflinger::Nvnflinger& nvnflinger);
+ Kernel::KProcess* process);
~ILibraryAppletProxy();
private:
@@ -47,7 +47,6 @@ private:
Result GetGlobalStateController(
Out<SharedPointer<IGlobalStateController>> out_global_state_controller);
- Nvnflinger::Nvnflinger& m_nvnflinger;
Kernel::KProcess* const m_process;
const std::shared_ptr<Applet> m_applet;
};
diff --git a/src/core/hle/service/am/service/library_applet_self_accessor.cpp b/src/core/hle/service/am/service/library_applet_self_accessor.cpp
index 7a3a86e88..330eb26f0 100644
--- a/src/core/hle/service/am/service/library_applet_self_accessor.cpp
+++ b/src/core/hle/service/am/service/library_applet_self_accessor.cpp
@@ -14,7 +14,8 @@
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/glue/glue_manager.h"
-#include "core/hle/service/ns/ns.h"
+#include "core/hle/service/ns/application_manager_interface.h"
+#include "core/hle/service/ns/service_getter_interface.h"
#include "core/hle/service/sm/sm.h"
namespace Service::AM {
@@ -256,11 +257,13 @@ Result ILibraryAppletSelfAccessor::GetMainAppletApplicationDesiredLanguage(
// Call IApplicationManagerInterface implementation.
auto& service_manager = system.ServiceManager();
- auto ns_am2 = service_manager.GetService<NS::NS>("ns:am2");
- auto app_man = ns_am2->GetApplicationManagerInterface();
+ auto ns_am2 = service_manager.GetService<NS::IServiceGetterInterface>("ns:am2");
+
+ std::shared_ptr<NS::IApplicationManagerInterface> app_man;
+ R_TRY(ns_am2->GetApplicationManagerInterface(&app_man));
// Get desired application language
- u8 desired_language{};
+ NS::ApplicationLanguage desired_language{};
R_TRY(app_man->GetApplicationDesiredLanguage(&desired_language, supported_languages));
// Convert to settings language code.
@@ -284,17 +287,17 @@ Result ILibraryAppletSelfAccessor::GetCurrentApplicationId(Out<u64> out_applicat
}
Result ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(
- Out<bool> out_no_users_available, Out<s32> out_users_count,
+ Out<bool> out_can_select_any_user, Out<s32> out_users_count,
OutArray<Common::UUID, BufferAttr_HipcMapAlias> out_users) {
const Service::Account::ProfileManager manager{};
- *out_no_users_available = true;
+ *out_can_select_any_user = false;
*out_users_count = -1;
LOG_INFO(Service_AM, "called");
if (manager.GetUserCount() > 0) {
- *out_no_users_available = false;
+ *out_can_select_any_user = true;
*out_users_count = static_cast<s32>(manager.GetUserCount());
const auto users = manager.GetAllUsers();
diff --git a/src/core/hle/service/am/service/library_applet_self_accessor.h b/src/core/hle/service/am/service/library_applet_self_accessor.h
index a9743569f..3e60393c2 100644
--- a/src/core/hle/service/am/service/library_applet_self_accessor.h
+++ b/src/core/hle/service/am/service/library_applet_self_accessor.h
@@ -71,7 +71,7 @@ private:
ErrorCode error_code, InLargeData<ErrorContext, BufferAttr_HipcMapAlias> error_context);
Result GetMainAppletApplicationDesiredLanguage(Out<u64> out_desired_language);
Result GetCurrentApplicationId(Out<u64> out_application_id);
- Result GetMainAppletAvailableUsers(Out<bool> out_no_users_available, Out<s32> out_users_count,
+ Result GetMainAppletAvailableUsers(Out<bool> out_can_select_any_user, Out<s32> out_users_count,
OutArray<Common::UUID, BufferAttr_HipcMapAlias> out_users);
Result ShouldSetGpuTimeSliceManually(Out<bool> out_should_set_gpu_time_slice_manually);
Result Cmd160(Out<u64> out_unknown0);
diff --git a/src/core/hle/service/am/service/self_controller.cpp b/src/core/hle/service/am/service/self_controller.cpp
index 5c4c13de1..06314407c 100644
--- a/src/core/hle/service/am/service/self_controller.cpp
+++ b/src/core/hle/service/am/service/self_controller.cpp
@@ -15,9 +15,9 @@
namespace Service::AM {
ISelfController::ISelfController(Core::System& system_, std::shared_ptr<Applet> applet,
- Kernel::KProcess* process, Nvnflinger::Nvnflinger& nvnflinger)
- : ServiceFramework{system_, "ISelfController"},
- m_nvnflinger{nvnflinger}, m_process{process}, m_applet{std::move(applet)} {
+ Kernel::KProcess* process)
+ : ServiceFramework{system_, "ISelfController"}, m_process{process}, m_applet{
+ std::move(applet)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, D<&ISelfController::Exit>, "Exit"},
@@ -72,9 +72,16 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr<Applet>
// clang-format on
RegisterHandlers(functions);
+
+ std::scoped_lock lk{m_applet->lock};
+ m_applet->display_layer_manager.Initialize(system, m_process, m_applet->applet_id,
+ m_applet->library_applet_mode);
}
-ISelfController::~ISelfController() = default;
+ISelfController::~ISelfController() {
+ std::scoped_lock lk{m_applet->lock};
+ m_applet->display_layer_manager.Finalize();
+}
Result ISelfController::Exit() {
LOG_DEBUG(Service_AM, "called");
@@ -212,48 +219,42 @@ Result ISelfController::SetAlbumImageOrientation(
Result ISelfController::IsSystemBufferSharingEnabled() {
LOG_INFO(Service_AM, "called");
- R_SUCCEED_IF(m_applet->system_buffer_manager.Initialize(
- &m_nvnflinger, m_process, m_applet->applet_id, m_applet->library_applet_mode));
- R_THROW(VI::ResultOperationFailed);
+
+ std::scoped_lock lk{m_applet->lock};
+ R_RETURN(m_applet->display_layer_manager.IsSystemBufferSharingEnabled());
}
Result ISelfController::GetSystemSharedBufferHandle(Out<u64> out_buffer_id) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
-
- R_TRY(this->IsSystemBufferSharingEnabled());
+ LOG_INFO(Service_AM, "called");
u64 layer_id;
- m_applet->system_buffer_manager.GetSystemSharedLayerHandle(out_buffer_id, &layer_id);
- R_SUCCEED();
+
+ std::scoped_lock lk{m_applet->lock};
+ R_RETURN(m_applet->display_layer_manager.GetSystemSharedLayerHandle(out_buffer_id, &layer_id));
}
Result ISelfController::GetSystemSharedLayerHandle(Out<u64> out_buffer_id, Out<u64> out_layer_id) {
- LOG_INFO(Service_AM, "(STUBBED) called");
-
- R_TRY(this->IsSystemBufferSharingEnabled());
+ LOG_INFO(Service_AM, "called");
- m_applet->system_buffer_manager.GetSystemSharedLayerHandle(out_buffer_id, out_layer_id);
- R_SUCCEED();
+ std::scoped_lock lk{m_applet->lock};
+ R_RETURN(
+ m_applet->display_layer_manager.GetSystemSharedLayerHandle(out_buffer_id, out_layer_id));
}
Result ISelfController::CreateManagedDisplayLayer(Out<u64> out_layer_id) {
LOG_INFO(Service_AM, "called");
- m_applet->managed_layer_holder.Initialize(&m_nvnflinger);
- m_applet->managed_layer_holder.CreateManagedDisplayLayer(out_layer_id);
-
- R_SUCCEED();
+ std::scoped_lock lk{m_applet->lock};
+ R_RETURN(m_applet->display_layer_manager.CreateManagedDisplayLayer(out_layer_id));
}
Result ISelfController::CreateManagedDisplaySeparableLayer(Out<u64> out_layer_id,
Out<u64> out_recording_layer_id) {
LOG_WARNING(Service_AM, "(STUBBED) called");
- m_applet->managed_layer_holder.Initialize(&m_nvnflinger);
- m_applet->managed_layer_holder.CreateManagedDisplaySeparableLayer(out_layer_id,
- out_recording_layer_id);
-
- R_SUCCEED();
+ std::scoped_lock lk{m_applet->lock};
+ R_RETURN(m_applet->display_layer_manager.CreateManagedDisplaySeparableLayer(
+ out_layer_id, out_recording_layer_id));
}
Result ISelfController::SetHandlesRequestToDisplay(bool enable) {
diff --git a/src/core/hle/service/am/service/self_controller.h b/src/core/hle/service/am/service/self_controller.h
index 01fa381a3..eca083cfe 100644
--- a/src/core/hle/service/am/service/self_controller.h
+++ b/src/core/hle/service/am/service/self_controller.h
@@ -23,7 +23,7 @@ struct Applet;
class ISelfController final : public ServiceFramework<ISelfController> {
public:
explicit ISelfController(Core::System& system_, std::shared_ptr<Applet> applet,
- Kernel::KProcess* process, Nvnflinger::Nvnflinger& nvnflinger);
+ Kernel::KProcess* process);
~ISelfController() override;
private:
@@ -64,7 +64,6 @@ private:
Result SaveCurrentScreenshot(Capture::AlbumReportOption album_report_option);
Result SetRecordVolumeMuted(bool muted);
- Nvnflinger::Nvnflinger& m_nvnflinger;
Kernel::KProcess* const m_process;
const std::shared_ptr<Applet> m_applet;
};
diff --git a/src/core/hle/service/am/service/system_applet_proxy.cpp b/src/core/hle/service/am/service/system_applet_proxy.cpp
index 5ec509d2e..d1871ef9b 100644
--- a/src/core/hle/service/am/service/system_applet_proxy.cpp
+++ b/src/core/hle/service/am/service/system_applet_proxy.cpp
@@ -19,10 +19,9 @@
namespace Service::AM {
ISystemAppletProxy::ISystemAppletProxy(Core::System& system_, std::shared_ptr<Applet> applet,
- Kernel::KProcess* process,
- Nvnflinger::Nvnflinger& nvnflinger)
- : ServiceFramework{system_, "ISystemAppletProxy"},
- m_nvnflinger{nvnflinger}, m_process{process}, m_applet{std::move(applet)} {
+ Kernel::KProcess* process)
+ : ServiceFramework{system_, "ISystemAppletProxy"}, m_process{process}, m_applet{
+ std::move(applet)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, D<&ISystemAppletProxy::GetCommonStateGetter>, "GetCommonStateGetter"},
@@ -83,8 +82,7 @@ Result ISystemAppletProxy::GetWindowController(
Result ISystemAppletProxy::GetSelfController(
Out<SharedPointer<ISelfController>> out_self_controller) {
LOG_DEBUG(Service_AM, "called");
- *out_self_controller =
- std::make_shared<ISelfController>(system, m_applet, m_process, m_nvnflinger);
+ *out_self_controller = std::make_shared<ISelfController>(system, m_applet, m_process);
R_SUCCEED();
}
diff --git a/src/core/hle/service/am/service/system_applet_proxy.h b/src/core/hle/service/am/service/system_applet_proxy.h
index 3d5040315..67cd50e03 100644
--- a/src/core/hle/service/am/service/system_applet_proxy.h
+++ b/src/core/hle/service/am/service/system_applet_proxy.h
@@ -25,7 +25,7 @@ class IWindowController;
class ISystemAppletProxy final : public ServiceFramework<ISystemAppletProxy> {
public:
explicit ISystemAppletProxy(Core::System& system, std::shared_ptr<Applet> applet,
- Kernel::KProcess* process, Nvnflinger::Nvnflinger& nvnflinger);
+ Kernel::KProcess* process);
~ISystemAppletProxy();
private:
@@ -46,7 +46,6 @@ private:
Result GetGlobalStateController(
Out<SharedPointer<IGlobalStateController>> out_global_state_controller);
- Nvnflinger::Nvnflinger& m_nvnflinger;
Kernel::KProcess* const m_process;
const std::shared_ptr<Applet> m_applet;
};
diff --git a/src/core/hle/service/am/service/window_controller.cpp b/src/core/hle/service/am/service/window_controller.cpp
index b874ecb91..99a4f50a2 100644
--- a/src/core/hle/service/am/service/window_controller.cpp
+++ b/src/core/hle/service/am/service/window_controller.cpp
@@ -63,7 +63,7 @@ Result IWindowController::RejectToChangeIntoBackground() {
}
Result IWindowController::SetAppletWindowVisibility(bool visible) {
- m_applet->system_buffer_manager.SetWindowVisibility(visible);
+ m_applet->display_layer_manager.SetWindowVisibility(visible);
m_applet->hid_registration.EnableAppletToGetInput(visible);
if (visible) {
diff --git a/src/core/hle/service/am/system_buffer_manager.cpp b/src/core/hle/service/am/system_buffer_manager.cpp
deleted file mode 100644
index 48923fe41..000000000
--- a/src/core/hle/service/am/system_buffer_manager.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "core/hle/service/am/system_buffer_manager.h"
-#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
-#include "core/hle/service/nvnflinger/nvnflinger.h"
-#include "core/hle/service/vi/vi_results.h"
-
-namespace Service::AM {
-
-SystemBufferManager::SystemBufferManager() = default;
-
-SystemBufferManager::~SystemBufferManager() {
- if (!m_nvnflinger) {
- return;
- }
-
- // Clean up shared layers.
- if (m_buffer_sharing_enabled) {
- m_nvnflinger->GetSystemBufferManager().Finalize(m_process);
- }
-}
-
-bool SystemBufferManager::Initialize(Nvnflinger::Nvnflinger* nvnflinger, Kernel::KProcess* process,
- AppletId applet_id, LibraryAppletMode mode) {
- if (m_nvnflinger) {
- return m_buffer_sharing_enabled;
- }
-
- m_process = process;
- m_nvnflinger = nvnflinger;
- m_buffer_sharing_enabled = false;
- m_system_shared_buffer_id = 0;
- m_system_shared_layer_id = 0;
-
- if (applet_id <= AppletId::Application) {
- return false;
- }
-
- Nvnflinger::LayerBlending blending = Nvnflinger::LayerBlending::None;
- if (mode == LibraryAppletMode::PartialForeground ||
- mode == LibraryAppletMode::PartialForegroundIndirectDisplay) {
- blending = Nvnflinger::LayerBlending::Coverage;
- }
-
- const auto display_id = m_nvnflinger->OpenDisplay("Default").value();
- const auto res = m_nvnflinger->GetSystemBufferManager().Initialize(
- m_process, &m_system_shared_buffer_id, &m_system_shared_layer_id, display_id, blending);
-
- if (res.IsSuccess()) {
- m_buffer_sharing_enabled = true;
- m_nvnflinger->SetLayerVisibility(m_system_shared_layer_id, m_visible);
- }
-
- return m_buffer_sharing_enabled;
-}
-
-void SystemBufferManager::SetWindowVisibility(bool visible) {
- if (m_visible == visible) {
- return;
- }
-
- m_visible = visible;
-
- if (m_nvnflinger) {
- m_nvnflinger->SetLayerVisibility(m_system_shared_layer_id, m_visible);
- }
-}
-
-Result SystemBufferManager::WriteAppletCaptureBuffer(bool* out_was_written,
- s32* out_fbshare_layer_index) {
- if (!m_buffer_sharing_enabled) {
- return VI::ResultPermissionDenied;
- }
-
- return m_nvnflinger->GetSystemBufferManager().WriteAppletCaptureBuffer(out_was_written,
- out_fbshare_layer_index);
-}
-
-} // namespace Service::AM
diff --git a/src/core/hle/service/am/system_buffer_manager.h b/src/core/hle/service/am/system_buffer_manager.h
deleted file mode 100644
index 0690f68b6..000000000
--- a/src/core/hle/service/am/system_buffer_manager.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <set>
-
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-
-#include "core/hle/service/am/am_types.h"
-
-namespace Kernel {
-class KProcess;
-}
-
-namespace Service::Nvnflinger {
-class Nvnflinger;
-}
-
-union Result;
-
-namespace Service::AM {
-
-class SystemBufferManager {
-public:
- SystemBufferManager();
- ~SystemBufferManager();
-
- bool Initialize(Nvnflinger::Nvnflinger* flinger, Kernel::KProcess* process, AppletId applet_id,
- LibraryAppletMode mode);
-
- void GetSystemSharedLayerHandle(u64* out_system_shared_buffer_id,
- u64* out_system_shared_layer_id) {
- *out_system_shared_buffer_id = m_system_shared_buffer_id;
- *out_system_shared_layer_id = m_system_shared_layer_id;
- }
-
- void SetWindowVisibility(bool visible);
-
- Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_fbshare_layer_index);
-
-private:
- Kernel::KProcess* m_process{};
- Nvnflinger::Nvnflinger* m_nvnflinger{};
- bool m_buffer_sharing_enabled{};
- bool m_visible{true};
- u64 m_system_shared_buffer_id{};
- u64 m_system_shared_layer_id{};
-};
-
-} // namespace Service::AM
diff --git a/src/core/hle/service/audio/audctl.cpp b/src/core/hle/service/audio/audctl.cpp
deleted file mode 100644
index cf4bb4034..000000000
--- a/src/core/hle/service/audio/audctl.cpp
+++ /dev/null
@@ -1,201 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "common/logging/log.h"
-#include "core/hle/service/audio/audctl.h"
-#include "core/hle/service/ipc_helpers.h"
-#include "core/hle/service/set/system_settings_server.h"
-#include "core/hle/service/sm/sm.h"
-
-namespace Service::Audio {
-
-AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "GetTargetVolume"},
- {1, nullptr, "SetTargetVolume"},
- {2, &AudCtl::GetTargetVolumeMin, "GetTargetVolumeMin"},
- {3, &AudCtl::GetTargetVolumeMax, "GetTargetVolumeMax"},
- {4, nullptr, "IsTargetMute"},
- {5, nullptr, "SetTargetMute"},
- {6, nullptr, "IsTargetConnected"},
- {7, nullptr, "SetDefaultTarget"},
- {8, nullptr, "GetDefaultTarget"},
- {9, &AudCtl::GetAudioOutputMode, "GetAudioOutputMode"},
- {10, &AudCtl::SetAudioOutputMode, "SetAudioOutputMode"},
- {11, nullptr, "SetForceMutePolicy"},
- {12, &AudCtl::GetForceMutePolicy, "GetForceMutePolicy"},
- {13, &AudCtl::GetOutputModeSetting, "GetOutputModeSetting"},
- {14, &AudCtl::SetOutputModeSetting, "SetOutputModeSetting"},
- {15, nullptr, "SetOutputTarget"},
- {16, nullptr, "SetInputTargetForceEnabled"},
- {17, &AudCtl::SetHeadphoneOutputLevelMode, "SetHeadphoneOutputLevelMode"},
- {18, &AudCtl::GetHeadphoneOutputLevelMode, "GetHeadphoneOutputLevelMode"},
- {19, nullptr, "AcquireAudioVolumeUpdateEventForPlayReport"},
- {20, nullptr, "AcquireAudioOutputDeviceUpdateEventForPlayReport"},
- {21, nullptr, "GetAudioOutputTargetForPlayReport"},
- {22, nullptr, "NotifyHeadphoneVolumeWarningDisplayedEvent"},
- {23, nullptr, "SetSystemOutputMasterVolume"},
- {24, nullptr, "GetSystemOutputMasterVolume"},
- {25, nullptr, "GetAudioVolumeDataForPlayReport"},
- {26, nullptr, "UpdateHeadphoneSettings"},
- {27, nullptr, "SetVolumeMappingTableForDev"},
- {28, nullptr, "GetAudioOutputChannelCountForPlayReport"},
- {29, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"},
- {30, &AudCtl::SetSpeakerAutoMuteEnabled, "SetSpeakerAutoMuteEnabled"},
- {31, &AudCtl::IsSpeakerAutoMuteEnabled, "IsSpeakerAutoMuteEnabled"},
- {32, nullptr, "GetActiveOutputTarget"},
- {33, nullptr, "GetTargetDeviceInfo"},
- {34, nullptr, "AcquireTargetNotification"},
- {35, nullptr, "SetHearingProtectionSafeguardTimerRemainingTimeForDebug"},
- {36, nullptr, "GetHearingProtectionSafeguardTimerRemainingTimeForDebug"},
- {37, nullptr, "SetHearingProtectionSafeguardEnabled"},
- {38, nullptr, "IsHearingProtectionSafeguardEnabled"},
- {39, nullptr, "IsHearingProtectionSafeguardMonitoringOutputForDebug"},
- {40, nullptr, "GetSystemInformationForDebug"},
- {41, nullptr, "SetVolumeButtonLongPressTime"},
- {42, nullptr, "SetNativeVolumeForDebug"},
- {10000, nullptr, "NotifyAudioOutputTargetForPlayReport"},
- {10001, nullptr, "NotifyAudioOutputChannelCountForPlayReport"},
- {10002, nullptr, "NotifyUnsupportedUsbOutputDeviceAttachedForPlayReport"},
- {10100, nullptr, "GetAudioVolumeDataForPlayReport"},
- {10101, nullptr, "BindAudioVolumeUpdateEventForPlayReport"},
- {10102, nullptr, "BindAudioOutputTargetUpdateEventForPlayReport"},
- {10103, nullptr, "GetAudioOutputTargetForPlayReport"},
- {10104, nullptr, "GetAudioOutputChannelCountForPlayReport"},
- {10105, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"},
- {10106, nullptr, "GetDefaultAudioOutputTargetForPlayReport"},
- {50000, nullptr, "SetAnalogInputBoostGainForPrototyping"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-
- m_set_sys =
- system.ServiceManager().GetService<Service::Set::ISystemSettingsServer>("set:sys", true);
-}
-
-AudCtl::~AudCtl() = default;
-
-void AudCtl::GetTargetVolumeMin(HLERequestContext& ctx) {
- LOG_DEBUG(Audio, "called.");
-
- // This service function is currently hardcoded on the
- // actual console to this value (as of 8.0.0).
- constexpr s32 target_min_volume = 0;
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(target_min_volume);
-}
-
-void AudCtl::GetTargetVolumeMax(HLERequestContext& ctx) {
- LOG_DEBUG(Audio, "called.");
-
- // This service function is currently hardcoded on the
- // actual console to this value (as of 8.0.0).
- constexpr s32 target_max_volume = 15;
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(target_max_volume);
-}
-
-void AudCtl::GetAudioOutputMode(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto target{rp.PopEnum<Set::AudioOutputModeTarget>()};
-
- Set::AudioOutputMode output_mode{};
- const auto result = m_set_sys->GetAudioOutputMode(&output_mode, target);
-
- LOG_INFO(Service_SET, "called, target={}, output_mode={}", target, output_mode);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(result);
- rb.PushEnum(output_mode);
-}
-
-void AudCtl::SetAudioOutputMode(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto target{rp.PopEnum<Set::AudioOutputModeTarget>()};
- const auto output_mode{rp.PopEnum<Set::AudioOutputMode>()};
-
- const auto result = m_set_sys->SetAudioOutputMode(target, output_mode);
-
- LOG_INFO(Service_SET, "called, target={}, output_mode={}", target, output_mode);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void AudCtl::GetForceMutePolicy(HLERequestContext& ctx) {
- LOG_WARNING(Audio, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(ForceMutePolicy::Disable);
-}
-
-void AudCtl::GetOutputModeSetting(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto target{rp.PopEnum<Set::AudioOutputModeTarget>()};
-
- LOG_WARNING(Audio, "(STUBBED) called, target={}", target);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(Set::AudioOutputMode::ch_7_1);
-}
-
-void AudCtl::SetOutputModeSetting(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto target{rp.PopEnum<Set::AudioOutputModeTarget>()};
- const auto output_mode{rp.PopEnum<Set::AudioOutputMode>()};
-
- LOG_INFO(Service_SET, "called, target={}, output_mode={}", target, output_mode);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void AudCtl::SetHeadphoneOutputLevelMode(HLERequestContext& ctx) {
- LOG_WARNING(Audio, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void AudCtl::GetHeadphoneOutputLevelMode(HLERequestContext& ctx) {
- LOG_WARNING(Audio, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(HeadphoneOutputLevelMode::Normal);
-}
-
-void AudCtl::SetSpeakerAutoMuteEnabled(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto is_speaker_auto_mute_enabled{rp.Pop<bool>()};
-
- LOG_WARNING(Audio, "(STUBBED) called, is_speaker_auto_mute_enabled={}",
- is_speaker_auto_mute_enabled);
-
- const auto result = m_set_sys->SetSpeakerAutoMuteFlag(is_speaker_auto_mute_enabled);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void AudCtl::IsSpeakerAutoMuteEnabled(HLERequestContext& ctx) {
- bool is_speaker_auto_mute_enabled{};
- const auto result = m_set_sys->GetSpeakerAutoMuteFlag(&is_speaker_auto_mute_enabled);
-
- LOG_WARNING(Audio, "(STUBBED) called, is_speaker_auto_mute_enabled={}",
- is_speaker_auto_mute_enabled);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(result);
- rb.Push<u8>(is_speaker_auto_mute_enabled);
-}
-
-} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audctl.h b/src/core/hle/service/audio/audctl.h
deleted file mode 100644
index 4c90ead70..000000000
--- a/src/core/hle/service/audio/audctl.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include "core/hle/service/service.h"
-
-namespace Core {
-class System;
-}
-
-namespace Service::Set {
-class ISystemSettingsServer;
-}
-
-namespace Service::Audio {
-
-class AudCtl final : public ServiceFramework<AudCtl> {
-public:
- explicit AudCtl(Core::System& system_);
- ~AudCtl() override;
-
-private:
- enum class ForceMutePolicy {
- Disable,
- SpeakerMuteOnHeadphoneUnplugged,
- };
-
- enum class HeadphoneOutputLevelMode {
- Normal,
- HighPower,
- };
-
- void GetTargetVolumeMin(HLERequestContext& ctx);
- void GetTargetVolumeMax(HLERequestContext& ctx);
- void GetAudioOutputMode(HLERequestContext& ctx);
- void SetAudioOutputMode(HLERequestContext& ctx);
- void GetForceMutePolicy(HLERequestContext& ctx);
- void GetOutputModeSetting(HLERequestContext& ctx);
- void SetOutputModeSetting(HLERequestContext& ctx);
- void SetHeadphoneOutputLevelMode(HLERequestContext& ctx);
- void GetHeadphoneOutputLevelMode(HLERequestContext& ctx);
- void SetSpeakerAutoMuteEnabled(HLERequestContext& ctx);
- void IsSpeakerAutoMuteEnabled(HLERequestContext& ctx);
- void AcquireTargetNotification(HLERequestContext& ctx);
-
- std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
-};
-
-} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp
index dccd16309..44af030eb 100644
--- a/src/core/hle/service/audio/audio.cpp
+++ b/src/core/hle/service/audio/audio.cpp
@@ -2,9 +2,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
-#include "core/hle/service/audio/audctl.h"
#include "core/hle/service/audio/audin_u.h"
#include "core/hle/service/audio/audio.h"
+#include "core/hle/service/audio/audio_controller.h"
#include "core/hle/service/audio/audout_u.h"
#include "core/hle/service/audio/audrec_a.h"
#include "core/hle/service/audio/audrec_u.h"
@@ -18,7 +18,7 @@ namespace Service::Audio {
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
- server_manager->RegisterNamedService("audctl", std::make_shared<AudCtl>(system));
+ server_manager->RegisterNamedService("audctl", std::make_shared<IAudioController>(system));
server_manager->RegisterNamedService("audout:u", std::make_shared<AudOutU>(system));
server_manager->RegisterNamedService("audin:u", std::make_shared<AudInU>(system));
server_manager->RegisterNamedService("audrec:a", std::make_shared<AudRecA>(system));
diff --git a/src/core/hle/service/audio/audio_controller.cpp b/src/core/hle/service/audio/audio_controller.cpp
new file mode 100644
index 000000000..a6da66d0f
--- /dev/null
+++ b/src/core/hle/service/audio/audio_controller.cpp
@@ -0,0 +1,174 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/logging/log.h"
+#include "core/hle/service/audio/audio_controller.h"
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ipc_helpers.h"
+#include "core/hle/service/set/system_settings_server.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Service::Audio {
+
+IAudioController::IAudioController(Core::System& system_)
+ : ServiceFramework{system_, "audctl"}, service_context{system, "audctl"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetTargetVolume"},
+ {1, nullptr, "SetTargetVolume"},
+ {2, C<&IAudioController::GetTargetVolumeMin>, "GetTargetVolumeMin"},
+ {3, C<&IAudioController::GetTargetVolumeMax>, "GetTargetVolumeMax"},
+ {4, nullptr, "IsTargetMute"},
+ {5, nullptr, "SetTargetMute"},
+ {6, nullptr, "IsTargetConnected"},
+ {7, nullptr, "SetDefaultTarget"},
+ {8, nullptr, "GetDefaultTarget"},
+ {9, C<&IAudioController::GetAudioOutputMode>, "GetAudioOutputMode"},
+ {10, C<&IAudioController::SetAudioOutputMode>, "SetAudioOutputMode"},
+ {11, nullptr, "SetForceMutePolicy"},
+ {12, C<&IAudioController::GetForceMutePolicy>, "GetForceMutePolicy"},
+ {13, C<&IAudioController::GetOutputModeSetting>, "GetOutputModeSetting"},
+ {14, C<&IAudioController::SetOutputModeSetting>, "SetOutputModeSetting"},
+ {15, nullptr, "SetOutputTarget"},
+ {16, nullptr, "SetInputTargetForceEnabled"},
+ {17, C<&IAudioController::SetHeadphoneOutputLevelMode>, "SetHeadphoneOutputLevelMode"},
+ {18, C<&IAudioController::GetHeadphoneOutputLevelMode>, "GetHeadphoneOutputLevelMode"},
+ {19, nullptr, "AcquireAudioVolumeUpdateEventForPlayReport"},
+ {20, nullptr, "AcquireAudioOutputDeviceUpdateEventForPlayReport"},
+ {21, nullptr, "GetAudioOutputTargetForPlayReport"},
+ {22, nullptr, "NotifyHeadphoneVolumeWarningDisplayedEvent"},
+ {23, nullptr, "SetSystemOutputMasterVolume"},
+ {24, nullptr, "GetSystemOutputMasterVolume"},
+ {25, nullptr, "GetAudioVolumeDataForPlayReport"},
+ {26, nullptr, "UpdateHeadphoneSettings"},
+ {27, nullptr, "SetVolumeMappingTableForDev"},
+ {28, nullptr, "GetAudioOutputChannelCountForPlayReport"},
+ {29, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"},
+ {30, C<&IAudioController::SetSpeakerAutoMuteEnabled>, "SetSpeakerAutoMuteEnabled"},
+ {31, C<&IAudioController::IsSpeakerAutoMuteEnabled>, "IsSpeakerAutoMuteEnabled"},
+ {32, nullptr, "GetActiveOutputTarget"},
+ {33, nullptr, "GetTargetDeviceInfo"},
+ {34, C<&IAudioController::AcquireTargetNotification>, "AcquireTargetNotification"},
+ {35, nullptr, "SetHearingProtectionSafeguardTimerRemainingTimeForDebug"},
+ {36, nullptr, "GetHearingProtectionSafeguardTimerRemainingTimeForDebug"},
+ {37, nullptr, "SetHearingProtectionSafeguardEnabled"},
+ {38, nullptr, "IsHearingProtectionSafeguardEnabled"},
+ {39, nullptr, "IsHearingProtectionSafeguardMonitoringOutputForDebug"},
+ {40, nullptr, "GetSystemInformationForDebug"},
+ {41, nullptr, "SetVolumeButtonLongPressTime"},
+ {42, nullptr, "SetNativeVolumeForDebug"},
+ {10000, nullptr, "NotifyAudioOutputTargetForPlayReport"},
+ {10001, nullptr, "NotifyAudioOutputChannelCountForPlayReport"},
+ {10002, nullptr, "NotifyUnsupportedUsbOutputDeviceAttachedForPlayReport"},
+ {10100, nullptr, "GetAudioVolumeDataForPlayReport"},
+ {10101, nullptr, "BindAudioVolumeUpdateEventForPlayReport"},
+ {10102, nullptr, "BindAudioOutputTargetUpdateEventForPlayReport"},
+ {10103, nullptr, "GetAudioOutputTargetForPlayReport"},
+ {10104, nullptr, "GetAudioOutputChannelCountForPlayReport"},
+ {10105, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"},
+ {10106, nullptr, "GetDefaultAudioOutputTargetForPlayReport"},
+ {50000, nullptr, "SetAnalogInputBoostGainForPrototyping"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ m_set_sys =
+ system.ServiceManager().GetService<Service::Set::ISystemSettingsServer>("set:sys", true);
+ notification_event = service_context.CreateEvent("IAudioController:NotificationEvent");
+}
+
+IAudioController::~IAudioController() {
+ service_context.CloseEvent(notification_event);
+};
+
+Result IAudioController::GetTargetVolumeMin(Out<s32> out_target_min_volume) {
+ LOG_DEBUG(Audio, "called.");
+
+ // This service function is currently hardcoded on the
+ // actual console to this value (as of 8.0.0).
+ *out_target_min_volume = 0;
+ R_SUCCEED();
+}
+
+Result IAudioController::GetTargetVolumeMax(Out<s32> out_target_max_volume) {
+ LOG_DEBUG(Audio, "called.");
+
+ // This service function is currently hardcoded on the
+ // actual console to this value (as of 8.0.0).
+ *out_target_max_volume = 15;
+ R_SUCCEED();
+}
+
+Result IAudioController::GetAudioOutputMode(Out<Set::AudioOutputMode> out_output_mode,
+ Set::AudioOutputModeTarget target) {
+ const auto result = m_set_sys->GetAudioOutputMode(out_output_mode, target);
+
+ LOG_INFO(Service_SET, "called, target={}, output_mode={}", target, *out_output_mode);
+ R_RETURN(result);
+}
+
+Result IAudioController::SetAudioOutputMode(Set::AudioOutputModeTarget target,
+ Set::AudioOutputMode output_mode) {
+ LOG_INFO(Service_SET, "called, target={}, output_mode={}", target, output_mode);
+
+ R_RETURN(m_set_sys->SetAudioOutputMode(target, output_mode));
+}
+
+Result IAudioController::GetForceMutePolicy(Out<ForceMutePolicy> out_mute_policy) {
+ LOG_WARNING(Audio, "(STUBBED) called");
+
+ // Removed on FW 13.2.1+
+ *out_mute_policy = ForceMutePolicy::Disable;
+ R_SUCCEED();
+}
+
+Result IAudioController::GetOutputModeSetting(Out<Set::AudioOutputMode> out_output_mode,
+ Set::AudioOutputModeTarget target) {
+ LOG_WARNING(Audio, "(STUBBED) called, target={}", target);
+
+ *out_output_mode = Set::AudioOutputMode::ch_7_1;
+ R_SUCCEED();
+}
+
+Result IAudioController::SetOutputModeSetting(Set::AudioOutputModeTarget target,
+ Set::AudioOutputMode output_mode) {
+ LOG_INFO(Service_SET, "called, target={}, output_mode={}", target, output_mode);
+ R_SUCCEED();
+}
+
+Result IAudioController::SetHeadphoneOutputLevelMode(HeadphoneOutputLevelMode output_level_mode) {
+ LOG_WARNING(Audio, "(STUBBED) called");
+ R_SUCCEED();
+}
+
+Result IAudioController::GetHeadphoneOutputLevelMode(
+ Out<HeadphoneOutputLevelMode> out_output_level_mode) {
+ LOG_INFO(Audio, "called");
+
+ *out_output_level_mode = HeadphoneOutputLevelMode::Normal;
+ R_SUCCEED();
+}
+
+Result IAudioController::SetSpeakerAutoMuteEnabled(bool is_speaker_auto_mute_enabled) {
+ LOG_INFO(Audio, "called, is_speaker_auto_mute_enabled={}", is_speaker_auto_mute_enabled);
+
+ R_RETURN(m_set_sys->SetSpeakerAutoMuteFlag(is_speaker_auto_mute_enabled));
+}
+
+Result IAudioController::IsSpeakerAutoMuteEnabled(Out<bool> out_is_speaker_auto_mute_enabled) {
+ const auto result = m_set_sys->GetSpeakerAutoMuteFlag(out_is_speaker_auto_mute_enabled);
+
+ LOG_INFO(Audio, "called, is_speaker_auto_mute_enabled={}", *out_is_speaker_auto_mute_enabled);
+ R_RETURN(result);
+}
+
+Result IAudioController::AcquireTargetNotification(
+ OutCopyHandle<Kernel::KReadableEvent> out_notification_event) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ *out_notification_event = &notification_event->GetReadableEvent();
+ R_SUCCEED();
+}
+
+} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audio_controller.h b/src/core/hle/service/audio/audio_controller.h
new file mode 100644
index 000000000..9e8514373
--- /dev/null
+++ b/src/core/hle/service/audio/audio_controller.h
@@ -0,0 +1,58 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+#include "core/hle/service/set/settings_types.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::Set {
+class ISystemSettingsServer;
+}
+
+namespace Service::Audio {
+
+class IAudioController final : public ServiceFramework<IAudioController> {
+public:
+ explicit IAudioController(Core::System& system_);
+ ~IAudioController() override;
+
+private:
+ enum class ForceMutePolicy {
+ Disable,
+ SpeakerMuteOnHeadphoneUnplugged,
+ };
+
+ enum class HeadphoneOutputLevelMode {
+ Normal,
+ HighPower,
+ };
+
+ Result GetTargetVolumeMin(Out<s32> out_target_min_volume);
+ Result GetTargetVolumeMax(Out<s32> out_target_max_volume);
+ Result GetAudioOutputMode(Out<Set::AudioOutputMode> out_output_mode,
+ Set::AudioOutputModeTarget target);
+ Result SetAudioOutputMode(Set::AudioOutputModeTarget target, Set::AudioOutputMode output_mode);
+ Result GetForceMutePolicy(Out<ForceMutePolicy> out_mute_policy);
+ Result GetOutputModeSetting(Out<Set::AudioOutputMode> out_output_mode,
+ Set::AudioOutputModeTarget target);
+ Result SetOutputModeSetting(Set::AudioOutputModeTarget target,
+ Set::AudioOutputMode output_mode);
+ Result SetHeadphoneOutputLevelMode(HeadphoneOutputLevelMode output_level_mode);
+ Result GetHeadphoneOutputLevelMode(Out<HeadphoneOutputLevelMode> out_output_level_mode);
+ Result SetSpeakerAutoMuteEnabled(bool is_speaker_auto_mute_enabled);
+ Result IsSpeakerAutoMuteEnabled(Out<bool> out_is_speaker_auto_mute_enabled);
+ Result AcquireTargetNotification(OutCopyHandle<Kernel::KReadableEvent> out_notification_event);
+
+ KernelHelpers::ServiceContext service_context;
+
+ Kernel::KEvent* notification_event;
+ std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
+};
+
+} // namespace Service::Audio
diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp
index 47ff072c5..52228b830 100644
--- a/src/core/hle/service/caps/caps_a.cpp
+++ b/src/core/hle/service/caps/caps_a.cpp
@@ -16,7 +16,7 @@ IAlbumAccessorService::IAlbumAccessorService(Core::System& system_,
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetAlbumFileCount"},
- {1, nullptr, "GetAlbumFileList"},
+ {1, C<&IAlbumAccessorService::GetAlbumFileList>, "GetAlbumFileList"},
{2, nullptr, "LoadAlbumFile"},
{3, C<&IAlbumAccessorService::DeleteAlbumFile>, "DeleteAlbumFile"},
{4, nullptr, "StorageCopyAlbumFile"},
@@ -62,6 +62,15 @@ IAlbumAccessorService::IAlbumAccessorService(Core::System& system_,
IAlbumAccessorService::~IAlbumAccessorService() = default;
+Result IAlbumAccessorService::GetAlbumFileList(
+ Out<u64> out_count, AlbumStorage storage,
+ OutArray<AlbumEntry, BufferAttr_HipcMapAlias> out_entries) {
+ LOG_INFO(Service_Capture, "called, storage={}", storage);
+
+ const Result result = manager->GetAlbumFileList(out_entries, *out_count, storage, 0);
+ R_RETURN(TranslateResult(result));
+}
+
Result IAlbumAccessorService::DeleteAlbumFile(AlbumFileId file_id) {
LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}",
file_id.application_id, file_id.storage, file_id.type);
diff --git a/src/core/hle/service/caps/caps_a.h b/src/core/hle/service/caps/caps_a.h
index 2cb9b4547..c7a5208e3 100644
--- a/src/core/hle/service/caps/caps_a.h
+++ b/src/core/hle/service/caps/caps_a.h
@@ -21,6 +21,9 @@ public:
~IAlbumAccessorService() override;
private:
+ Result GetAlbumFileList(Out<u64> out_count, AlbumStorage storage,
+ OutArray<AlbumEntry, BufferAttr_HipcMapAlias> out_entries);
+
Result DeleteAlbumFile(AlbumFileId file_id);
Result IsAlbumMounted(Out<bool> out_is_mounted, AlbumStorage storage);
diff --git a/src/core/hle/service/erpt/erpt.cpp b/src/core/hle/service/erpt/erpt.cpp
index 3ea862fad..39ae3a723 100644
--- a/src/core/hle/service/erpt/erpt.cpp
+++ b/src/core/hle/service/erpt/erpt.cpp
@@ -3,6 +3,8 @@
#include <memory>
+#include "common/logging/log.h"
+#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/erpt/erpt.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
@@ -15,7 +17,7 @@ public:
explicit ErrorReportContext(Core::System& system_) : ServiceFramework{system_, "erpt:c"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "SubmitContext"},
+ {0, C<&ErrorReportContext::SubmitContext>, "SubmitContext"},
{1, nullptr, "CreateReportV0"},
{2, nullptr, "SetInitialLaunchSettingsCompletionTime"},
{3, nullptr, "ClearInitialLaunchSettingsCompletionTime"},
@@ -36,6 +38,14 @@ public:
RegisterHandlers(functions);
}
+
+private:
+ Result SubmitContext(InBuffer<BufferAttr_HipcMapAlias> buffer_a,
+ InBuffer<BufferAttr_HipcMapAlias> buffer_b) {
+ LOG_WARNING(Service_SET, "(STUBBED) called, buffer_a_size={}, buffer_b_size={}",
+ buffer_a.size(), buffer_b.size());
+ R_SUCCEED();
+ }
};
class ErrorReportSession final : public ServiceFramework<ErrorReportSession> {
diff --git a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp
index 63c2d3a58..2d49f30c8 100644
--- a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp
@@ -336,7 +336,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
{1012, nullptr, "GetFsStackUsage"},
{1013, nullptr, "UnsetSaveDataRootPath"},
{1014, nullptr, "OutputMultiProgramTagAccessLog"},
- {1016, nullptr, "FlushAccessLogOnSdCard"},
+ {1016, &FSP_SRV::FlushAccessLogOnSdCard, "FlushAccessLogOnSdCard"},
{1017, nullptr, "OutputApplicationInfoAccessLog"},
{1018, nullptr, "SetDebugOption"},
{1019, nullptr, "UnsetDebugOption"},
@@ -706,6 +706,13 @@ void FSP_SRV::GetProgramIndexForAccessLog(HLERequestContext& ctx) {
rb.Push(access_log_program_index);
}
+void FSP_SRV::FlushAccessLogOnSdCard(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_FS, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void FSP_SRV::GetCacheStorageSize(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto index{rp.Pop<s32>()};
diff --git a/src/core/hle/service/filesystem/fsp/fsp_srv.h b/src/core/hle/service/filesystem/fsp/fsp_srv.h
index 26980af99..59406e6f9 100644
--- a/src/core/hle/service/filesystem/fsp/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp/fsp_srv.h
@@ -58,6 +58,7 @@ private:
void SetGlobalAccessLogMode(HLERequestContext& ctx);
void GetGlobalAccessLogMode(HLERequestContext& ctx);
void OutputAccessLogToSdCard(HLERequestContext& ctx);
+ void FlushAccessLogOnSdCard(HLERequestContext& ctx);
void GetProgramIndexForAccessLog(HLERequestContext& ctx);
void OpenMultiCommitManager(HLERequestContext& ctx);
void GetCacheStorageSize(HLERequestContext& ctx);
diff --git a/src/core/hle/service/glue/time/manager.cpp b/src/core/hle/service/glue/time/manager.cpp
index cad755fa7..059ac3fc9 100644
--- a/src/core/hle/service/glue/time/manager.cpp
+++ b/src/core/hle/service/glue/time/manager.cpp
@@ -186,6 +186,10 @@ TimeManager::TimeManager(Core::System& system)
}
}
+TimeManager::~TimeManager() {
+ ResetTimeZoneBinary();
+}
+
Result TimeManager::SetupStandardSteadyClockCore() {
Common::UUID external_clock_source_id{};
auto res = m_set_sys->GetExternalSteadyClockSourceId(&external_clock_source_id);
diff --git a/src/core/hle/service/glue/time/manager.h b/src/core/hle/service/glue/time/manager.h
index 1de93f8f9..bb4b65049 100644
--- a/src/core/hle/service/glue/time/manager.h
+++ b/src/core/hle/service/glue/time/manager.h
@@ -26,6 +26,7 @@ namespace Service::Glue::Time {
class TimeManager {
public:
explicit TimeManager(Core::System& system);
+ ~TimeManager();
std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
diff --git a/src/core/hle/service/glue/time/static.cpp b/src/core/hle/service/glue/time/static.cpp
index ec9b0efb1..b801faef2 100644
--- a/src/core/hle/service/glue/time/static.cpp
+++ b/src/core/hle/service/glue/time/static.cpp
@@ -142,16 +142,18 @@ Result StaticService::SetStandardSteadyClockInternalOffset(s64 offset_ns) {
}
Result StaticService::GetStandardSteadyClockRtcValue(Out<s64> out_rtc_value) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_rtc_value={}", *out_rtc_value); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_rtc_value={}", *out_rtc_value);
+ };
R_RETURN(m_standard_steady_clock_resource.GetRtcTimeInSeconds(*out_rtc_value));
}
Result StaticService::IsStandardUserSystemClockAutomaticCorrectionEnabled(
Out<bool> out_automatic_correction) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
LOG_DEBUG(Service_Time, "called. out_automatic_correction={}", *out_automatic_correction);
- });
+ };
R_RETURN(m_wrapped_service->IsStandardUserSystemClockAutomaticCorrectionEnabled(
out_automatic_correction));
@@ -166,21 +168,27 @@ Result StaticService::SetStandardUserSystemClockAutomaticCorrectionEnabled(
}
Result StaticService::GetStandardUserSystemClockInitialYear(Out<s32> out_year) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_year={}", *out_year); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_year={}", *out_year);
+ };
R_RETURN(m_set_sys->GetSettingsItemValueImpl<s32>(*out_year, "time",
"standard_user_clock_initial_year"));
}
Result StaticService::IsStandardNetworkSystemClockAccuracySufficient(Out<bool> out_is_sufficient) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_is_sufficient={}", *out_is_sufficient); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_is_sufficient={}", *out_is_sufficient);
+ };
R_RETURN(m_wrapped_service->IsStandardNetworkSystemClockAccuracySufficient(out_is_sufficient));
}
Result StaticService::GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(
Out<Service::PSC::Time::SteadyClockTimePoint> out_time_point) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_time_point={}", *out_time_point); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_time_point={}", *out_time_point);
+ };
R_RETURN(m_wrapped_service->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(
out_time_point));
@@ -188,15 +196,18 @@ Result StaticService::GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(
Result StaticService::CalculateMonotonicSystemClockBaseTimePoint(
Out<s64> out_time, const Service::PSC::Time::SystemClockContext& context) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. context={} out_time={}", context, *out_time); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. context={} out_time={}", context, *out_time);
+ };
R_RETURN(m_wrapped_service->CalculateMonotonicSystemClockBaseTimePoint(out_time, context));
}
Result StaticService::GetClockSnapshot(OutClockSnapshot out_snapshot,
Service::PSC::Time::TimeType type) {
- SCOPE_EXIT(
- { LOG_DEBUG(Service_Time, "called. type={} out_snapshot={}", type, *out_snapshot); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. type={} out_snapshot={}", type, *out_snapshot);
+ };
R_RETURN(m_wrapped_service->GetClockSnapshot(out_snapshot, type));
}
@@ -205,11 +216,11 @@ Result StaticService::GetClockSnapshotFromSystemClockContext(
Service::PSC::Time::TimeType type, OutClockSnapshot out_snapshot,
const Service::PSC::Time::SystemClockContext& user_context,
const Service::PSC::Time::SystemClockContext& network_context) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
LOG_DEBUG(Service_Time,
"called. type={} out_snapshot={} user_context={} network_context={}", type,
*out_snapshot, user_context, network_context);
- });
+ };
R_RETURN(m_wrapped_service->GetClockSnapshotFromSystemClockContext(
type, out_snapshot, user_context, network_context));
@@ -218,14 +229,18 @@ Result StaticService::GetClockSnapshotFromSystemClockContext(
Result StaticService::CalculateStandardUserSystemClockDifferenceByUser(Out<s64> out_time,
InClockSnapshot a,
InClockSnapshot b) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. a={} b={} out_time={}", *a, *b, *out_time); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. a={} b={} out_time={}", *a, *b, *out_time);
+ };
R_RETURN(m_wrapped_service->CalculateStandardUserSystemClockDifferenceByUser(out_time, a, b));
}
Result StaticService::CalculateSpanBetween(Out<s64> out_time, InClockSnapshot a,
InClockSnapshot b) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. a={} b={} out_time={}", *a, *b, *out_time); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. a={} b={} out_time={}", *a, *b, *out_time);
+ };
R_RETURN(m_wrapped_service->CalculateSpanBetween(out_time, a, b));
}
diff --git a/src/core/hle/service/glue/time/time_zone.cpp b/src/core/hle/service/glue/time/time_zone.cpp
index 36f163419..f4d0c87d5 100644
--- a/src/core/hle/service/glue/time/time_zone.cpp
+++ b/src/core/hle/service/glue/time/time_zone.cpp
@@ -57,7 +57,9 @@ TimeZoneService::~TimeZoneService() = default;
Result TimeZoneService::GetDeviceLocationName(
Out<Service::PSC::Time::LocationName> out_location_name) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_location_name={}", *out_location_name); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_location_name={}", *out_location_name);
+ };
R_RETURN(m_wrapped_service->GetDeviceLocationName(out_location_name));
}
@@ -94,7 +96,9 @@ Result TimeZoneService::SetDeviceLocationName(
}
Result TimeZoneService::GetTotalLocationNameCount(Out<u32> out_count) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_count={}", *out_count); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_count={}", *out_count);
+ };
R_RETURN(m_wrapped_service->GetTotalLocationNameCount(out_count));
}
@@ -102,10 +106,10 @@ Result TimeZoneService::GetTotalLocationNameCount(Out<u32> out_count) {
Result TimeZoneService::LoadLocationNameList(
Out<u32> out_count,
OutArray<Service::PSC::Time::LocationName, BufferAttr_HipcMapAlias> out_names, u32 index) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
LOG_DEBUG(Service_Time, "called. index={} out_count={} out_names[0]={} out_names[1]={}",
index, *out_count, out_names[0], out_names[1]);
- });
+ };
std::scoped_lock l{m_mutex};
R_RETURN(GetTimeZoneLocationList(*out_count, out_names, out_names.size(), index));
@@ -124,7 +128,9 @@ Result TimeZoneService::LoadTimeZoneRule(OutRule out_rule,
Result TimeZoneService::GetTimeZoneRuleVersion(
Out<Service::PSC::Time::RuleVersion> out_rule_version) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_rule_version={}", *out_rule_version); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_rule_version={}", *out_rule_version);
+ };
R_RETURN(m_wrapped_service->GetTimeZoneRuleVersion(out_rule_version));
}
@@ -132,10 +138,10 @@ Result TimeZoneService::GetTimeZoneRuleVersion(
Result TimeZoneService::GetDeviceLocationNameAndUpdatedTime(
Out<Service::PSC::Time::LocationName> location_name,
Out<Service::PSC::Time::SteadyClockTimePoint> out_time_point) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
LOG_DEBUG(Service_Time, "called. location_name={} out_time_point={}", *location_name,
*out_time_point);
- });
+ };
R_RETURN(m_wrapped_service->GetDeviceLocationNameAndUpdatedTime(location_name, out_time_point));
}
@@ -178,10 +184,10 @@ Result TimeZoneService::GetDeviceLocationNameOperationEventReadableHandle(
Result TimeZoneService::ToCalendarTime(
Out<Service::PSC::Time::CalendarTime> out_calendar_time,
Out<Service::PSC::Time::CalendarAdditionalInfo> out_additional_info, s64 time, InRule rule) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
LOG_DEBUG(Service_Time, "called. time={} out_calendar_time={} out_additional_info={}", time,
*out_calendar_time, *out_additional_info);
- });
+ };
R_RETURN(m_wrapped_service->ToCalendarTime(out_calendar_time, out_additional_info, time, rule));
}
@@ -189,10 +195,10 @@ Result TimeZoneService::ToCalendarTime(
Result TimeZoneService::ToCalendarTimeWithMyRule(
Out<Service::PSC::Time::CalendarTime> out_calendar_time,
Out<Service::PSC::Time::CalendarAdditionalInfo> out_additional_info, s64 time) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
LOG_DEBUG(Service_Time, "called. time={} out_calendar_time={} out_additional_info={}", time,
*out_calendar_time, *out_additional_info);
- });
+ };
R_RETURN(
m_wrapped_service->ToCalendarTimeWithMyRule(out_calendar_time, out_additional_info, time));
@@ -202,11 +208,11 @@ Result TimeZoneService::ToPosixTime(Out<u32> out_count,
OutArray<s64, BufferAttr_HipcPointer> out_times,
const Service::PSC::Time::CalendarTime& calendar_time,
InRule rule) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
LOG_DEBUG(Service_Time,
"called. calendar_time={} out_count={} out_times[0]={} out_times[1]={}",
calendar_time, *out_count, out_times[0], out_times[1]);
- });
+ };
R_RETURN(m_wrapped_service->ToPosixTime(out_count, out_times, calendar_time, rule));
}
@@ -214,11 +220,11 @@ Result TimeZoneService::ToPosixTime(Out<u32> out_count,
Result TimeZoneService::ToPosixTimeWithMyRule(
Out<u32> out_count, OutArray<s64, BufferAttr_HipcPointer> out_times,
const Service::PSC::Time::CalendarTime& calendar_time) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
LOG_DEBUG(Service_Time,
"called. calendar_time={} out_count={} out_times[0]={} out_times[1]={}",
calendar_time, *out_count, out_times[0], out_times[1]);
- });
+ };
R_RETURN(m_wrapped_service->ToPosixTimeWithMyRule(out_count, out_times, calendar_time));
}
diff --git a/src/core/hle/service/ns/account_proxy_interface.cpp b/src/core/hle/service/ns/account_proxy_interface.cpp
new file mode 100644
index 000000000..e5041af66
--- /dev/null
+++ b/src/core/hle/service/ns/account_proxy_interface.cpp
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/ns/account_proxy_interface.h"
+
+namespace Service::NS {
+
+IAccountProxyInterface::IAccountProxyInterface(Core::System& system_)
+ : ServiceFramework{system_, "IAccountProxyInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "CreateUserAccount"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IAccountProxyInterface::~IAccountProxyInterface() = default;
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/account_proxy_interface.h b/src/core/hle/service/ns/account_proxy_interface.h
new file mode 100644
index 000000000..e944d2a75
--- /dev/null
+++ b/src/core/hle/service/ns/account_proxy_interface.h
@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+class IAccountProxyInterface final : public ServiceFramework<IAccountProxyInterface> {
+public:
+ explicit IAccountProxyInterface(Core::System& system_);
+ ~IAccountProxyInterface() override;
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/application_manager_interface.cpp b/src/core/hle/service/ns/application_manager_interface.cpp
new file mode 100644
index 000000000..2e3a44c0d
--- /dev/null
+++ b/src/core/hle/service/ns/application_manager_interface.cpp
@@ -0,0 +1,519 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/ns/application_manager_interface.h"
+#include "core/hle/service/ns/content_management_interface.h"
+#include "core/hle/service/ns/read_only_application_control_data_interface.h"
+
+namespace Service::NS {
+
+IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_)
+ : ServiceFramework{system_, "IApplicationManagerInterface"},
+ service_context{system, "IApplicationManagerInterface"},
+ record_update_system_event{service_context}, sd_card_mount_status_event{service_context},
+ gamecard_update_detection_event{service_context},
+ gamecard_mount_status_event{service_context}, gamecard_mount_failure_event{service_context} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, D<&IApplicationManagerInterface::ListApplicationRecord>, "ListApplicationRecord"},
+ {1, nullptr, "GenerateApplicationRecordCount"},
+ {2, D<&IApplicationManagerInterface::GetApplicationRecordUpdateSystemEvent>, "GetApplicationRecordUpdateSystemEvent"},
+ {3, nullptr, "GetApplicationViewDeprecated"},
+ {4, nullptr, "DeleteApplicationEntity"},
+ {5, nullptr, "DeleteApplicationCompletely"},
+ {6, nullptr, "IsAnyApplicationEntityRedundant"},
+ {7, nullptr, "DeleteRedundantApplicationEntity"},
+ {8, nullptr, "IsApplicationEntityMovable"},
+ {9, nullptr, "MoveApplicationEntity"},
+ {11, nullptr, "CalculateApplicationOccupiedSize"},
+ {16, nullptr, "PushApplicationRecord"},
+ {17, nullptr, "ListApplicationRecordContentMeta"},
+ {19, nullptr, "LaunchApplicationOld"},
+ {21, nullptr, "GetApplicationContentPath"},
+ {22, nullptr, "TerminateApplication"},
+ {23, nullptr, "ResolveApplicationContentPath"},
+ {26, nullptr, "BeginInstallApplication"},
+ {27, nullptr, "DeleteApplicationRecord"},
+ {30, nullptr, "RequestApplicationUpdateInfo"},
+ {31, nullptr, "Unknown31"},
+ {32, nullptr, "CancelApplicationDownload"},
+ {33, nullptr, "ResumeApplicationDownload"},
+ {35, nullptr, "UpdateVersionList"},
+ {36, nullptr, "PushLaunchVersion"},
+ {37, nullptr, "ListRequiredVersion"},
+ {38, D<&IApplicationManagerInterface::CheckApplicationLaunchVersion>, "CheckApplicationLaunchVersion"},
+ {39, nullptr, "CheckApplicationLaunchRights"},
+ {40, nullptr, "GetApplicationLogoData"},
+ {41, nullptr, "CalculateApplicationDownloadRequiredSize"},
+ {42, nullptr, "CleanupSdCard"},
+ {43, D<&IApplicationManagerInterface::CheckSdCardMountStatus>, "CheckSdCardMountStatus"},
+ {44, D<&IApplicationManagerInterface::GetSdCardMountStatusChangedEvent>, "GetSdCardMountStatusChangedEvent"},
+ {45, nullptr, "GetGameCardAttachmentEvent"},
+ {46, nullptr, "GetGameCardAttachmentInfo"},
+ {47, nullptr, "GetTotalSpaceSize"},
+ {48, D<&IApplicationManagerInterface::GetFreeSpaceSize>, "GetFreeSpaceSize"},
+ {49, nullptr, "GetSdCardRemovedEvent"},
+ {52, D<&IApplicationManagerInterface::GetGameCardUpdateDetectionEvent>, "GetGameCardUpdateDetectionEvent"},
+ {53, nullptr, "DisableApplicationAutoDelete"},
+ {54, nullptr, "EnableApplicationAutoDelete"},
+ {55, D<&IApplicationManagerInterface::GetApplicationDesiredLanguage>, "GetApplicationDesiredLanguage"},
+ {56, nullptr, "SetApplicationTerminateResult"},
+ {57, nullptr, "ClearApplicationTerminateResult"},
+ {58, nullptr, "GetLastSdCardMountUnexpectedResult"},
+ {59, D<&IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"},
+ {60, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
+ {61, nullptr, "GetBackgroundDownloadStressTaskInfo"},
+ {62, nullptr, "GetGameCardStopper"},
+ {63, nullptr, "IsSystemProgramInstalled"},
+ {64, nullptr, "StartApplyDeltaTask"},
+ {65, nullptr, "GetRequestServerStopper"},
+ {66, nullptr, "GetBackgroundApplyDeltaStressTaskInfo"},
+ {67, nullptr, "CancelApplicationApplyDelta"},
+ {68, nullptr, "ResumeApplicationApplyDelta"},
+ {69, nullptr, "CalculateApplicationApplyDeltaRequiredSize"},
+ {70, D<&IApplicationManagerInterface::ResumeAll>, "ResumeAll"},
+ {71, D<&IApplicationManagerInterface::GetStorageSize>, "GetStorageSize"},
+ {80, nullptr, "RequestDownloadApplication"},
+ {81, nullptr, "RequestDownloadAddOnContent"},
+ {82, nullptr, "DownloadApplication"},
+ {83, nullptr, "CheckApplicationResumeRights"},
+ {84, nullptr, "GetDynamicCommitEvent"},
+ {85, nullptr, "RequestUpdateApplication2"},
+ {86, nullptr, "EnableApplicationCrashReport"},
+ {87, nullptr, "IsApplicationCrashReportEnabled"},
+ {90, nullptr, "BoostSystemMemoryResourceLimit"},
+ {91, nullptr, "DeprecatedLaunchApplication"},
+ {92, nullptr, "GetRunningApplicationProgramId"},
+ {93, nullptr, "GetMainApplicationProgramIndex"},
+ {94, nullptr, "LaunchApplication"},
+ {95, nullptr, "GetApplicationLaunchInfo"},
+ {96, nullptr, "AcquireApplicationLaunchInfo"},
+ {97, nullptr, "GetMainApplicationProgramIndexByApplicationLaunchInfo"},
+ {98, nullptr, "EnableApplicationAllThreadDumpOnCrash"},
+ {99, nullptr, "LaunchDevMenu"},
+ {100, nullptr, "ResetToFactorySettings"},
+ {101, nullptr, "ResetToFactorySettingsWithoutUserSaveData"},
+ {102, nullptr, "ResetToFactorySettingsForRefurbishment"},
+ {103, nullptr, "ResetToFactorySettingsWithPlatformRegion"},
+ {104, nullptr, "ResetToFactorySettingsWithPlatformRegionAuthentication"},
+ {105, nullptr, "RequestResetToFactorySettingsSecurely"},
+ {106, nullptr, "RequestResetToFactorySettingsWithPlatformRegionAuthenticationSecurely"},
+ {200, nullptr, "CalculateUserSaveDataStatistics"},
+ {201, nullptr, "DeleteUserSaveDataAll"},
+ {210, nullptr, "DeleteUserSystemSaveData"},
+ {211, nullptr, "DeleteSaveData"},
+ {220, nullptr, "UnregisterNetworkServiceAccount"},
+ {221, nullptr, "UnregisterNetworkServiceAccountWithUserSaveDataDeletion"},
+ {300, nullptr, "GetApplicationShellEvent"},
+ {301, nullptr, "PopApplicationShellEventInfo"},
+ {302, nullptr, "LaunchLibraryApplet"},
+ {303, nullptr, "TerminateLibraryApplet"},
+ {304, nullptr, "LaunchSystemApplet"},
+ {305, nullptr, "TerminateSystemApplet"},
+ {306, nullptr, "LaunchOverlayApplet"},
+ {307, nullptr, "TerminateOverlayApplet"},
+ {400, D<&IApplicationManagerInterface::GetApplicationControlData>, "GetApplicationControlData"},
+ {401, nullptr, "InvalidateAllApplicationControlCache"},
+ {402, nullptr, "RequestDownloadApplicationControlData"},
+ {403, nullptr, "GetMaxApplicationControlCacheCount"},
+ {404, nullptr, "InvalidateApplicationControlCache"},
+ {405, nullptr, "ListApplicationControlCacheEntryInfo"},
+ {406, nullptr, "GetApplicationControlProperty"},
+ {407, nullptr, "ListApplicationTitle"},
+ {408, nullptr, "ListApplicationIcon"},
+ {502, nullptr, "RequestCheckGameCardRegistration"},
+ {503, nullptr, "RequestGameCardRegistrationGoldPoint"},
+ {504, nullptr, "RequestRegisterGameCard"},
+ {505, D<&IApplicationManagerInterface::GetGameCardMountFailureEvent>, "GetGameCardMountFailureEvent"},
+ {506, nullptr, "IsGameCardInserted"},
+ {507, nullptr, "EnsureGameCardAccess"},
+ {508, nullptr, "GetLastGameCardMountFailureResult"},
+ {509, nullptr, "ListApplicationIdOnGameCard"},
+ {510, nullptr, "GetGameCardPlatformRegion"},
+ {600, nullptr, "CountApplicationContentMeta"},
+ {601, nullptr, "ListApplicationContentMetaStatus"},
+ {602, nullptr, "ListAvailableAddOnContent"},
+ {603, nullptr, "GetOwnedApplicationContentMetaStatus"},
+ {604, nullptr, "RegisterContentsExternalKey"},
+ {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
+ {606, nullptr, "GetContentMetaStorage"},
+ {607, nullptr, "ListAvailableAddOnContent"},
+ {609, nullptr, "ListAvailabilityAssuredAddOnContent"},
+ {610, nullptr, "GetInstalledContentMetaStorage"},
+ {611, nullptr, "PrepareAddOnContent"},
+ {700, nullptr, "PushDownloadTaskList"},
+ {701, nullptr, "ClearTaskStatusList"},
+ {702, nullptr, "RequestDownloadTaskList"},
+ {703, nullptr, "RequestEnsureDownloadTask"},
+ {704, nullptr, "ListDownloadTaskStatus"},
+ {705, nullptr, "RequestDownloadTaskListData"},
+ {800, nullptr, "RequestVersionList"},
+ {801, nullptr, "ListVersionList"},
+ {802, nullptr, "RequestVersionListData"},
+ {900, nullptr, "GetApplicationRecord"},
+ {901, nullptr, "GetApplicationRecordProperty"},
+ {902, nullptr, "EnableApplicationAutoUpdate"},
+ {903, nullptr, "DisableApplicationAutoUpdate"},
+ {904, nullptr, "TouchApplication"},
+ {905, nullptr, "RequestApplicationUpdate"},
+ {906, D<&IApplicationManagerInterface::IsApplicationUpdateRequested>, "IsApplicationUpdateRequested"},
+ {907, nullptr, "WithdrawApplicationUpdateRequest"},
+ {908, nullptr, "ListApplicationRecordInstalledContentMeta"},
+ {909, nullptr, "WithdrawCleanupAddOnContentsWithNoRightsRecommendation"},
+ {910, nullptr, "HasApplicationRecord"},
+ {911, nullptr, "SetPreInstalledApplication"},
+ {912, nullptr, "ClearPreInstalledApplicationFlag"},
+ {913, nullptr, "ListAllApplicationRecord"},
+ {914, nullptr, "HideApplicationRecord"},
+ {915, nullptr, "ShowApplicationRecord"},
+ {916, nullptr, "IsApplicationAutoDeleteDisabled"},
+ {1000, nullptr, "RequestVerifyApplicationDeprecated"},
+ {1001, nullptr, "CorruptApplicationForDebug"},
+ {1002, nullptr, "RequestVerifyAddOnContentsRights"},
+ {1003, nullptr, "RequestVerifyApplication"},
+ {1004, nullptr, "CorruptContentForDebug"},
+ {1200, nullptr, "NeedsUpdateVulnerability"},
+ {1300, D<&IApplicationManagerInterface::IsAnyApplicationEntityInstalled>, "IsAnyApplicationEntityInstalled"},
+ {1301, nullptr, "DeleteApplicationContentEntities"},
+ {1302, nullptr, "CleanupUnrecordedApplicationEntity"},
+ {1303, nullptr, "CleanupAddOnContentsWithNoRights"},
+ {1304, nullptr, "DeleteApplicationContentEntity"},
+ {1305, nullptr, "TryDeleteRunningApplicationEntity"},
+ {1306, nullptr, "TryDeleteRunningApplicationCompletely"},
+ {1307, nullptr, "TryDeleteRunningApplicationContentEntities"},
+ {1308, nullptr, "DeleteApplicationCompletelyForDebug"},
+ {1309, nullptr, "CleanupUnavailableAddOnContents"},
+ {1310, nullptr, "RequestMoveApplicationEntity"},
+ {1311, nullptr, "EstimateSizeToMove"},
+ {1312, nullptr, "HasMovableEntity"},
+ {1313, nullptr, "CleanupOrphanContents"},
+ {1314, nullptr, "CheckPreconditionSatisfiedToMove"},
+ {1400, nullptr, "PrepareShutdown"},
+ {1500, nullptr, "FormatSdCard"},
+ {1501, nullptr, "NeedsSystemUpdateToFormatSdCard"},
+ {1502, nullptr, "GetLastSdCardFormatUnexpectedResult"},
+ {1504, nullptr, "InsertSdCard"},
+ {1505, nullptr, "RemoveSdCard"},
+ {1506, nullptr, "GetSdCardStartupStatus"},
+ {1600, nullptr, "GetSystemSeedForPseudoDeviceId"},
+ {1601, nullptr, "ResetSystemSeedForPseudoDeviceId"},
+ {1700, nullptr, "ListApplicationDownloadingContentMeta"},
+ {1701, D<&IApplicationManagerInterface::GetApplicationView>, "GetApplicationView"},
+ {1702, nullptr, "GetApplicationDownloadTaskStatus"},
+ {1703, nullptr, "GetApplicationViewDownloadErrorContext"},
+ {1704, D<&IApplicationManagerInterface::GetApplicationViewWithPromotionInfo>, "GetApplicationViewWithPromotionInfo"},
+ {1705, nullptr, "IsPatchAutoDeletableApplication"},
+ {1800, nullptr, "IsNotificationSetupCompleted"},
+ {1801, nullptr, "GetLastNotificationInfoCount"},
+ {1802, nullptr, "ListLastNotificationInfo"},
+ {1803, nullptr, "ListNotificationTask"},
+ {1900, nullptr, "IsActiveAccount"},
+ {1901, nullptr, "RequestDownloadApplicationPrepurchasedRights"},
+ {1902, nullptr, "GetApplicationTicketInfo"},
+ {1903, nullptr, "RequestDownloadApplicationPrepurchasedRightsForAccount"},
+ {2000, nullptr, "GetSystemDeliveryInfo"},
+ {2001, nullptr, "SelectLatestSystemDeliveryInfo"},
+ {2002, nullptr, "VerifyDeliveryProtocolVersion"},
+ {2003, nullptr, "GetApplicationDeliveryInfo"},
+ {2004, nullptr, "HasAllContentsToDeliver"},
+ {2005, nullptr, "CompareApplicationDeliveryInfo"},
+ {2006, nullptr, "CanDeliverApplication"},
+ {2007, nullptr, "ListContentMetaKeyToDeliverApplication"},
+ {2008, nullptr, "NeedsSystemUpdateToDeliverApplication"},
+ {2009, nullptr, "EstimateRequiredSize"},
+ {2010, nullptr, "RequestReceiveApplication"},
+ {2011, nullptr, "CommitReceiveApplication"},
+ {2012, nullptr, "GetReceiveApplicationProgress"},
+ {2013, nullptr, "RequestSendApplication"},
+ {2014, nullptr, "GetSendApplicationProgress"},
+ {2015, nullptr, "CompareSystemDeliveryInfo"},
+ {2016, nullptr, "ListNotCommittedContentMeta"},
+ {2017, nullptr, "CreateDownloadTask"},
+ {2018, nullptr, "GetApplicationDeliveryInfoHash"},
+ {2050, D<&IApplicationManagerInterface::GetApplicationRightsOnClient>, "GetApplicationRightsOnClient"},
+ {2051, nullptr, "InvalidateRightsIdCache"},
+ {2100, D<&IApplicationManagerInterface::GetApplicationTerminateResult>, "GetApplicationTerminateResult"},
+ {2101, nullptr, "GetRawApplicationTerminateResult"},
+ {2150, nullptr, "CreateRightsEnvironment"},
+ {2151, nullptr, "DestroyRightsEnvironment"},
+ {2152, nullptr, "ActivateRightsEnvironment"},
+ {2153, nullptr, "DeactivateRightsEnvironment"},
+ {2154, nullptr, "ForceActivateRightsContextForExit"},
+ {2155, nullptr, "UpdateRightsEnvironmentStatus"},
+ {2156, nullptr, "CreateRightsEnvironmentForMicroApplication"},
+ {2160, nullptr, "AddTargetApplicationToRightsEnvironment"},
+ {2161, nullptr, "SetUsersToRightsEnvironment"},
+ {2170, nullptr, "GetRightsEnvironmentStatus"},
+ {2171, nullptr, "GetRightsEnvironmentStatusChangedEvent"},
+ {2180, nullptr, "RequestExtendRightsInRightsEnvironment"},
+ {2181, nullptr, "GetResultOfExtendRightsInRightsEnvironment"},
+ {2182, nullptr, "SetActiveRightsContextUsingStateToRightsEnvironment"},
+ {2190, nullptr, "GetRightsEnvironmentHandleForApplication"},
+ {2199, nullptr, "GetRightsEnvironmentCountForDebug"},
+ {2200, nullptr, "GetGameCardApplicationCopyIdentifier"},
+ {2201, nullptr, "GetInstalledApplicationCopyIdentifier"},
+ {2250, nullptr, "RequestReportActiveELicence"},
+ {2300, nullptr, "ListEventLog"},
+ {2350, nullptr, "PerformAutoUpdateByApplicationId"},
+ {2351, nullptr, "RequestNoDownloadRightsErrorResolution"},
+ {2352, nullptr, "RequestResolveNoDownloadRightsError"},
+ {2353, nullptr, "GetApplicationDownloadTaskInfo"},
+ {2354, nullptr, "PrioritizeApplicationBackgroundTask"},
+ {2355, nullptr, "PreferStorageEfficientUpdate"},
+ {2356, nullptr, "RequestStorageEfficientUpdatePreferable"},
+ {2357, nullptr, "EnableMultiCoreDownload"},
+ {2358, nullptr, "DisableMultiCoreDownload"},
+ {2359, nullptr, "IsMultiCoreDownloadEnabled"},
+ {2400, nullptr, "GetPromotionInfo"},
+ {2401, nullptr, "CountPromotionInfo"},
+ {2402, nullptr, "ListPromotionInfo"},
+ {2403, nullptr, "ImportPromotionJsonForDebug"},
+ {2404, nullptr, "ClearPromotionInfoForDebug"},
+ {2500, nullptr, "ConfirmAvailableTime"},
+ {2510, nullptr, "CreateApplicationResource"},
+ {2511, nullptr, "GetApplicationResource"},
+ {2513, nullptr, "LaunchMicroApplication"},
+ {2514, nullptr, "ClearTaskOfAsyncTaskManager"},
+ {2515, nullptr, "CleanupAllPlaceHolderAndFragmentsIfNoTask"},
+ {2516, nullptr, "EnsureApplicationCertificate"},
+ {2517, nullptr, "CreateApplicationInstance"},
+ {2518, nullptr, "UpdateQualificationForDebug"},
+ {2519, nullptr, "IsQualificationTransitionSupported"},
+ {2520, nullptr, "IsQualificationTransitionSupportedByProcessId"},
+ {2521, nullptr, "GetRightsUserChangedEvent"},
+ {2522, nullptr, "IsRomRedirectionAvailable"},
+ {2800, nullptr, "GetApplicationIdOfPreomia"},
+ {3000, nullptr, "RegisterDeviceLockKey"},
+ {3001, nullptr, "UnregisterDeviceLockKey"},
+ {3002, nullptr, "VerifyDeviceLockKey"},
+ {3003, nullptr, "HideApplicationIcon"},
+ {3004, nullptr, "ShowApplicationIcon"},
+ {3005, nullptr, "HideApplicationTitle"},
+ {3006, nullptr, "ShowApplicationTitle"},
+ {3007, nullptr, "EnableGameCard"},
+ {3008, nullptr, "DisableGameCard"},
+ {3009, nullptr, "EnableLocalContentShare"},
+ {3010, nullptr, "DisableLocalContentShare"},
+ {3011, nullptr, "IsApplicationIconHidden"},
+ {3012, nullptr, "IsApplicationTitleHidden"},
+ {3013, nullptr, "IsGameCardEnabled"},
+ {3014, nullptr, "IsLocalContentShareEnabled"},
+ {3050, nullptr, "ListAssignELicenseTaskResult"},
+ {9999, nullptr, "GetApplicationCertificate"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IApplicationManagerInterface::~IApplicationManagerInterface() = default;
+
+Result IApplicationManagerInterface::GetApplicationControlData(
+ OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_actual_size,
+ ApplicationControlSource application_control_source, u64 application_id) {
+ LOG_DEBUG(Service_NS, "called");
+ R_RETURN(IReadOnlyApplicationControlDataInterface(system).GetApplicationControlData(
+ out_buffer, out_actual_size, application_control_source, application_id));
+}
+
+Result IApplicationManagerInterface::GetApplicationDesiredLanguage(
+ Out<ApplicationLanguage> out_desired_language, u32 supported_languages) {
+ LOG_DEBUG(Service_NS, "called");
+ R_RETURN(IReadOnlyApplicationControlDataInterface(system).GetApplicationDesiredLanguage(
+ out_desired_language, supported_languages));
+}
+
+Result IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode(
+ Out<u64> out_language_code, ApplicationLanguage application_language) {
+ LOG_DEBUG(Service_NS, "called");
+ R_RETURN(
+ IReadOnlyApplicationControlDataInterface(system).ConvertApplicationLanguageToLanguageCode(
+ out_language_code, application_language));
+}
+
+Result IApplicationManagerInterface::ListApplicationRecord(
+ OutArray<ApplicationRecord, BufferAttr_HipcMapAlias> out_records, Out<s32> out_count,
+ s32 offset) {
+ const auto limit = out_records.size();
+
+ LOG_WARNING(Service_NS, "(STUBBED) called");
+ const auto& cache = system.GetContentProviderUnion();
+ const auto installed_games = cache.ListEntriesFilterOrigin(
+ std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
+
+ size_t i = 0;
+ u8 ii = 24;
+
+ for (const auto& [slot, game] : installed_games) {
+ if (i >= limit) {
+ break;
+ }
+ if (game.title_id == 0 || game.title_id < 0x0100000000001FFFull) {
+ continue;
+ }
+ if (offset > 0) {
+ offset--;
+ continue;
+ }
+
+ ApplicationRecord record{};
+ record.application_id = game.title_id;
+ record.type = ApplicationRecordType::Installed;
+ record.unknown = 0; // 2 = needs update
+ record.unknown2 = ii++;
+
+ out_records[i++] = record;
+ }
+
+ *out_count = static_cast<s32>(i);
+ R_SUCCEED();
+}
+
+Result IApplicationManagerInterface::GetApplicationRecordUpdateSystemEvent(
+ OutCopyHandle<Kernel::KReadableEvent> out_event) {
+ LOG_WARNING(Service_NS, "(STUBBED) called");
+
+ record_update_system_event.Signal();
+ *out_event = record_update_system_event.GetHandle();
+
+ R_SUCCEED();
+}
+
+Result IApplicationManagerInterface::GetGameCardMountFailureEvent(
+ OutCopyHandle<Kernel::KReadableEvent> out_event) {
+ LOG_WARNING(Service_NS, "(STUBBED) called");
+ *out_event = gamecard_mount_failure_event.GetHandle();
+ R_SUCCEED();
+}
+
+Result IApplicationManagerInterface::IsAnyApplicationEntityInstalled(
+ Out<bool> out_is_any_application_entity_installed) {
+ LOG_WARNING(Service_NS, "(STUBBED) called");
+ *out_is_any_application_entity_installed = true;
+ R_SUCCEED();
+}
+
+Result IApplicationManagerInterface::GetApplicationView(
+ OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
+ InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
+ const auto size = std::min(out_application_views.size(), application_ids.size());
+ LOG_WARNING(Service_NS, "(STUBBED) called, size={}", application_ids.size());
+
+ for (size_t i = 0; i < size; i++) {
+ ApplicationView view{};
+ view.application_id = application_ids[i];
+ view.unk = 0x70000;
+ view.flags = 0x401f17;
+
+ out_application_views[i] = view;
+ }
+
+ R_SUCCEED();
+}
+
+Result IApplicationManagerInterface::GetApplicationViewWithPromotionInfo(
+ OutArray<ApplicationViewWithPromotionInfo, BufferAttr_HipcMapAlias> out_application_views,
+ InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
+ const auto size = std::min(out_application_views.size(), application_ids.size());
+ LOG_WARNING(Service_NS, "(STUBBED) called, size={}", application_ids.size());
+
+ for (size_t i = 0; i < size; i++) {
+ ApplicationViewWithPromotionInfo view{};
+ view.view.application_id = application_ids[i];
+ view.view.unk = 0x70000;
+ view.view.flags = 0x401f17;
+ view.promotion = {};
+
+ out_application_views[i] = view;
+ }
+
+ R_SUCCEED();
+}
+
+Result IApplicationManagerInterface::GetApplicationRightsOnClient(
+ OutArray<ApplicationRightsOnClient, BufferAttr_HipcMapAlias> out_rights, Out<u32> out_count,
+ Common::UUID account_id, u32 flags, u64 application_id) {
+ LOG_WARNING(Service_NS, "(STUBBED) called, flags={}, application_id={:016X}, account_id={}",
+ flags, application_id, account_id.FormattedString());
+
+ if (!out_rights.empty()) {
+ ApplicationRightsOnClient rights{};
+ rights.application_id = application_id;
+ rights.uid = account_id;
+ rights.flags = 0;
+ rights.flags2 = 0;
+
+ out_rights[0] = rights;
+ *out_count = 1;
+ } else {
+ *out_count = 0;
+ }
+
+ R_SUCCEED();
+}
+
+Result IApplicationManagerInterface::CheckSdCardMountStatus() {
+ LOG_DEBUG(Service_NS, "called");
+ R_RETURN(IContentManagementInterface(system).CheckSdCardMountStatus());
+}
+
+Result IApplicationManagerInterface::GetSdCardMountStatusChangedEvent(
+ OutCopyHandle<Kernel::KReadableEvent> out_event) {
+ LOG_WARNING(Service_NS, "(STUBBED) called");
+ *out_event = sd_card_mount_status_event.GetHandle();
+ R_SUCCEED();
+}
+
+Result IApplicationManagerInterface::GetFreeSpaceSize(Out<s64> out_free_space_size,
+ FileSys::StorageId storage_id) {
+ LOG_DEBUG(Service_NS, "called");
+ R_RETURN(IContentManagementInterface(system).GetFreeSpaceSize(out_free_space_size, storage_id));
+}
+
+Result IApplicationManagerInterface::GetGameCardUpdateDetectionEvent(
+ OutCopyHandle<Kernel::KReadableEvent> out_event) {
+ LOG_WARNING(Service_NS, "(STUBBED) called");
+ *out_event = gamecard_update_detection_event.GetHandle();
+ R_SUCCEED();
+}
+
+Result IApplicationManagerInterface::ResumeAll() {
+ LOG_WARNING(Service_NS, "(STUBBED) called");
+ R_SUCCEED();
+}
+
+Result IApplicationManagerInterface::GetStorageSize(Out<s64> out_total_space_size,
+ Out<s64> out_free_space_size,
+ FileSys::StorageId storage_id) {
+ LOG_INFO(Service_NS, "called, storage_id={}", storage_id);
+ *out_total_space_size = system.GetFileSystemController().GetTotalSpaceSize(storage_id);
+ *out_free_space_size = system.GetFileSystemController().GetFreeSpaceSize(storage_id);
+ R_SUCCEED();
+}
+
+Result IApplicationManagerInterface::IsApplicationUpdateRequested(Out<bool> out_update_required,
+ Out<u32> out_update_version,
+ u64 application_id) {
+ LOG_WARNING(Service_NS, "(STUBBED) called. application_id={:016X}", application_id);
+ *out_update_required = false;
+ *out_update_version = 0;
+ R_SUCCEED();
+}
+
+Result IApplicationManagerInterface::CheckApplicationLaunchVersion(u64 application_id) {
+ LOG_WARNING(Service_NS, "(STUBBED) called. application_id={:016X}", application_id);
+ R_SUCCEED();
+}
+
+Result IApplicationManagerInterface::GetApplicationTerminateResult(Out<Result> out_result,
+ u64 application_id) {
+ LOG_WARNING(Service_NS, "(STUBBED) called. application_id={:016X}", application_id);
+ *out_result = ResultSuccess;
+ R_SUCCEED();
+}
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/application_manager_interface.h b/src/core/hle/service/ns/application_manager_interface.h
new file mode 100644
index 000000000..350ec37ce
--- /dev/null
+++ b/src/core/hle/service/ns/application_manager_interface.h
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/ns/language.h"
+#include "core/hle/service/ns/ns_types.h"
+#include "core/hle/service/os/event.h"
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+class IApplicationManagerInterface final : public ServiceFramework<IApplicationManagerInterface> {
+public:
+ explicit IApplicationManagerInterface(Core::System& system_);
+ ~IApplicationManagerInterface() override;
+
+ Result GetApplicationControlData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
+ Out<u32> out_actual_size,
+ ApplicationControlSource application_control_source,
+ u64 application_id);
+ Result GetApplicationDesiredLanguage(Out<ApplicationLanguage> out_desired_language,
+ u32 supported_languages);
+ Result ConvertApplicationLanguageToLanguageCode(Out<u64> out_language_code,
+ ApplicationLanguage application_language);
+ Result ListApplicationRecord(OutArray<ApplicationRecord, BufferAttr_HipcMapAlias> out_records,
+ Out<s32> out_count, s32 offset);
+ Result GetApplicationRecordUpdateSystemEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
+ Result GetGameCardMountFailureEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
+ Result IsAnyApplicationEntityInstalled(Out<bool> out_is_any_application_entity_installed);
+ Result GetApplicationView(
+ OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
+ InArray<u64, BufferAttr_HipcMapAlias> application_ids);
+ Result GetApplicationViewWithPromotionInfo(
+ OutArray<ApplicationViewWithPromotionInfo, BufferAttr_HipcMapAlias> out_application_views,
+ InArray<u64, BufferAttr_HipcMapAlias> application_ids);
+ Result GetApplicationRightsOnClient(
+ OutArray<ApplicationRightsOnClient, BufferAttr_HipcMapAlias> out_rights, Out<u32> out_count,
+ Common::UUID account_id, u32 flags, u64 application_id);
+ Result CheckSdCardMountStatus();
+ Result GetSdCardMountStatusChangedEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
+ Result GetFreeSpaceSize(Out<s64> out_free_space_size, FileSys::StorageId storage_id);
+ Result GetGameCardUpdateDetectionEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
+ Result ResumeAll();
+ Result GetStorageSize(Out<s64> out_total_space_size, Out<s64> out_free_space_size,
+ FileSys::StorageId storage_id);
+ Result IsApplicationUpdateRequested(Out<bool> out_update_required, Out<u32> out_update_version,
+ u64 application_id);
+ Result CheckApplicationLaunchVersion(u64 application_id);
+ Result GetApplicationTerminateResult(Out<Result> out_result, u64 application_id);
+
+private:
+ KernelHelpers::ServiceContext service_context;
+ Event record_update_system_event;
+ Event sd_card_mount_status_event;
+ Event gamecard_update_detection_event;
+ Event gamecard_mount_status_event;
+ Event gamecard_mount_failure_event;
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/application_version_interface.cpp b/src/core/hle/service/ns/application_version_interface.cpp
new file mode 100644
index 000000000..b89e127db
--- /dev/null
+++ b/src/core/hle/service/ns/application_version_interface.cpp
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/ns/application_version_interface.h"
+
+namespace Service::NS {
+
+IApplicationVersionInterface::IApplicationVersionInterface(Core::System& system_)
+ : ServiceFramework{system_, "IApplicationVersionInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetLaunchRequiredVersion"},
+ {1, nullptr, "UpgradeLaunchRequiredVersion"},
+ {35, nullptr, "UpdateVersionList"},
+ {36, nullptr, "PushLaunchVersion"},
+ {37, nullptr, "ListRequiredVersion"},
+ {800, nullptr, "RequestVersionList"},
+ {801, nullptr, "ListVersionList"},
+ {802, nullptr, "RequestVersionListData"},
+ {900, nullptr, "ImportAutoUpdatePolicyJsonForDebug"},
+ {901, nullptr, "ListDefaultAutoUpdatePolicy"},
+ {902, nullptr, "ListAutoUpdatePolicyForSpecificApplication"},
+ {1000, nullptr, "PerformAutoUpdate"},
+ {1001, nullptr, "ListAutoUpdateSchedule"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IApplicationVersionInterface::~IApplicationVersionInterface() = default;
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/application_version_interface.h b/src/core/hle/service/ns/application_version_interface.h
new file mode 100644
index 000000000..b288cff1b
--- /dev/null
+++ b/src/core/hle/service/ns/application_version_interface.h
@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+class IApplicationVersionInterface final : public ServiceFramework<IApplicationVersionInterface> {
+public:
+ explicit IApplicationVersionInterface(Core::System& system_);
+ ~IApplicationVersionInterface() override;
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/content_management_interface.cpp b/src/core/hle/service/ns/content_management_interface.cpp
new file mode 100644
index 000000000..69bb3f6e4
--- /dev/null
+++ b/src/core/hle/service/ns/content_management_interface.cpp
@@ -0,0 +1,72 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/common_funcs.h"
+#include "core/core.h"
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/ns/content_management_interface.h"
+#include "core/hle/service/ns/ns_types.h"
+
+namespace Service::NS {
+
+IContentManagementInterface::IContentManagementInterface(Core::System& system_)
+ : ServiceFramework{system_, "IContentManagementInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {11, D<&IContentManagementInterface::CalculateApplicationOccupiedSize>, "CalculateApplicationOccupiedSize"},
+ {43, D<&IContentManagementInterface::CheckSdCardMountStatus>, "CheckSdCardMountStatus"},
+ {47, D<&IContentManagementInterface::GetTotalSpaceSize>, "GetTotalSpaceSize"},
+ {48, D<&IContentManagementInterface::GetFreeSpaceSize>, "GetFreeSpaceSize"},
+ {600, nullptr, "CountApplicationContentMeta"},
+ {601, nullptr, "ListApplicationContentMetaStatus"},
+ {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
+ {607, nullptr, "IsAnyApplicationRunning"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IContentManagementInterface::~IContentManagementInterface() = default;
+
+Result IContentManagementInterface::CalculateApplicationOccupiedSize(
+ Out<ApplicationOccupiedSize> out_size, u64 application_id) {
+ LOG_WARNING(Service_NS, "(STUBBED) called, application_id={:016X}", application_id);
+
+ using namespace Common::Literals;
+
+ constexpr ApplicationOccupiedSizeEntity stub_entity{
+ .storage_id = FileSys::StorageId::SdCard,
+ .app_size = 8_GiB,
+ .patch_size = 2_GiB,
+ .aoc_size = 12_MiB,
+ };
+
+ for (auto& entity : out_size->entities) {
+ entity = stub_entity;
+ }
+
+ R_SUCCEED();
+}
+
+Result IContentManagementInterface::CheckSdCardMountStatus() {
+ LOG_WARNING(Service_NS, "(STUBBED) called");
+ R_SUCCEED();
+}
+
+Result IContentManagementInterface::GetTotalSpaceSize(Out<s64> out_total_space_size,
+ FileSys::StorageId storage_id) {
+ LOG_INFO(Service_NS, "(STUBBED) called, storage_id={}", storage_id);
+ *out_total_space_size = system.GetFileSystemController().GetTotalSpaceSize(storage_id);
+ R_SUCCEED();
+}
+
+Result IContentManagementInterface::GetFreeSpaceSize(Out<s64> out_free_space_size,
+ FileSys::StorageId storage_id) {
+ LOG_INFO(Service_NS, "(STUBBED) called, storage_id={}", storage_id);
+ *out_free_space_size = system.GetFileSystemController().GetFreeSpaceSize(storage_id);
+ R_SUCCEED();
+}
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/content_management_interface.h b/src/core/hle/service/ns/content_management_interface.h
new file mode 100644
index 000000000..2894628e5
--- /dev/null
+++ b/src/core/hle/service/ns/content_management_interface.h
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/ns/ns_types.h"
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+class IContentManagementInterface final : public ServiceFramework<IContentManagementInterface> {
+public:
+ explicit IContentManagementInterface(Core::System& system_);
+ ~IContentManagementInterface() override;
+
+public:
+ Result CalculateApplicationOccupiedSize(Out<ApplicationOccupiedSize> out_size,
+ u64 application_id);
+ Result CheckSdCardMountStatus();
+ Result GetTotalSpaceSize(Out<s64> out_total_space_size, FileSys::StorageId storage_id);
+ Result GetFreeSpaceSize(Out<s64> out_free_space_size, FileSys::StorageId storage_id);
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/develop_interface.cpp b/src/core/hle/service/ns/develop_interface.cpp
new file mode 100644
index 000000000..880bdbebb
--- /dev/null
+++ b/src/core/hle/service/ns/develop_interface.cpp
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/ns/develop_interface.h"
+
+namespace Service::NS {
+
+IDevelopInterface::IDevelopInterface(Core::System& system_) : ServiceFramework{system_, "ns:dev"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "LaunchProgram"},
+ {1, nullptr, "TerminateProcess"},
+ {2, nullptr, "TerminateProgram"},
+ {4, nullptr, "GetShellEvent"},
+ {5, nullptr, "GetShellEventInfo"},
+ {6, nullptr, "TerminateApplication"},
+ {7, nullptr, "PrepareLaunchProgramFromHost"},
+ {8, nullptr, "LaunchApplicationFromHost"},
+ {9, nullptr, "LaunchApplicationWithStorageIdForDevelop"},
+ {10, nullptr, "IsSystemMemoryResourceLimitBoosted"},
+ {11, nullptr, "GetRunningApplicationProcessIdForDevelop"},
+ {12, nullptr, "SetCurrentApplicationRightsEnvironmentCanBeActiveForDevelop"},
+ {13, nullptr, "CreateApplicationResourceForDevelop"},
+ {14, nullptr, "IsPreomiaForDevelop"},
+ {15, nullptr, "GetApplicationProgramIdFromHost"},
+ {16, nullptr, "RefreshCachedDebugValues"},
+ {17, nullptr, "PrepareLaunchApplicationFromHost"},
+ {18, nullptr, "GetLaunchEvent"},
+ {19, nullptr, "GetLaunchResult"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IDevelopInterface::~IDevelopInterface() = default;
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/develop_interface.h b/src/core/hle/service/ns/develop_interface.h
new file mode 100644
index 000000000..a9f81ccd6
--- /dev/null
+++ b/src/core/hle/service/ns/develop_interface.h
@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+class IDevelopInterface final : public ServiceFramework<IDevelopInterface> {
+public:
+ explicit IDevelopInterface(Core::System& system_);
+ ~IDevelopInterface() override;
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/document_interface.cpp b/src/core/hle/service/ns/document_interface.cpp
new file mode 100644
index 000000000..51a1e46c0
--- /dev/null
+++ b/src/core/hle/service/ns/document_interface.cpp
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/core.h"
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ns/document_interface.h"
+
+namespace Service::NS {
+
+IDocumentInterface::IDocumentInterface(Core::System& system_)
+ : ServiceFramework{system_, "IDocumentInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {21, nullptr, "GetApplicationContentPath"},
+ {23, D<&IDocumentInterface::ResolveApplicationContentPath>, "ResolveApplicationContentPath"},
+ {92, D<&IDocumentInterface::GetRunningApplicationProgramId>, "GetRunningApplicationProgramId"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IDocumentInterface::~IDocumentInterface() = default;
+
+Result IDocumentInterface::ResolveApplicationContentPath(ContentPath content_path) {
+ LOG_WARNING(Service_NS, "(STUBBED) called, file_system_proxy_type={}, program_id={:016X}",
+ content_path.file_system_proxy_type, content_path.program_id);
+ R_SUCCEED();
+}
+
+Result IDocumentInterface::GetRunningApplicationProgramId(Out<u64> out_program_id,
+ u64 caller_program_id) {
+ LOG_WARNING(Service_NS, "(STUBBED) called, caller_program_id={:016X}", caller_program_id);
+ *out_program_id = system.GetApplicationProcessProgramID();
+ R_SUCCEED();
+}
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/document_interface.h b/src/core/hle/service/ns/document_interface.h
new file mode 100644
index 000000000..cd461652c
--- /dev/null
+++ b/src/core/hle/service/ns/document_interface.h
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/ns/ns_types.h"
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+class IDocumentInterface final : public ServiceFramework<IDocumentInterface> {
+public:
+ explicit IDocumentInterface(Core::System& system_);
+ ~IDocumentInterface() override;
+
+private:
+ Result ResolveApplicationContentPath(ContentPath content_path);
+ Result GetRunningApplicationProgramId(Out<u64> out_program_id, u64 caller_program_id);
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/download_task_interface.cpp b/src/core/hle/service/ns/download_task_interface.cpp
new file mode 100644
index 000000000..62dc7f187
--- /dev/null
+++ b/src/core/hle/service/ns/download_task_interface.cpp
@@ -0,0 +1,39 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ns/download_task_interface.h"
+
+namespace Service::NS {
+
+IDownloadTaskInterface::IDownloadTaskInterface(Core::System& system_)
+ : ServiceFramework{system_, "IDownloadTaskInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {701, nullptr, "ClearTaskStatusList"},
+ {702, nullptr, "RequestDownloadTaskList"},
+ {703, nullptr, "RequestEnsureDownloadTask"},
+ {704, nullptr, "ListDownloadTaskStatus"},
+ {705, nullptr, "RequestDownloadTaskListData"},
+ {706, nullptr, "TryCommitCurrentApplicationDownloadTask"},
+ {707, D<&IDownloadTaskInterface::EnableAutoCommit>, "EnableAutoCommit"},
+ {708, D<&IDownloadTaskInterface::DisableAutoCommit>, "DisableAutoCommit"},
+ {709, nullptr, "TriggerDynamicCommitEvent"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IDownloadTaskInterface::~IDownloadTaskInterface() = default;
+
+Result IDownloadTaskInterface::EnableAutoCommit() {
+ LOG_WARNING(Service_NS, "(STUBBED) called");
+ R_SUCCEED();
+}
+Result IDownloadTaskInterface::DisableAutoCommit() {
+ LOG_WARNING(Service_NS, "(STUBBED) called");
+ R_SUCCEED();
+}
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/download_task_interface.h b/src/core/hle/service/ns/download_task_interface.h
new file mode 100644
index 000000000..b1cb69cb8
--- /dev/null
+++ b/src/core/hle/service/ns/download_task_interface.h
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+class IDownloadTaskInterface final : public ServiceFramework<IDownloadTaskInterface> {
+public:
+ explicit IDownloadTaskInterface(Core::System& system_);
+ ~IDownloadTaskInterface() override;
+
+private:
+ Result EnableAutoCommit();
+ Result DisableAutoCommit();
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/dynamic_rights_interface.cpp b/src/core/hle/service/ns/dynamic_rights_interface.cpp
new file mode 100644
index 000000000..ce81e203f
--- /dev/null
+++ b/src/core/hle/service/ns/dynamic_rights_interface.cpp
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ns/dynamic_rights_interface.h"
+
+namespace Service::NS {
+
+IDynamicRightsInterface::IDynamicRightsInterface(Core::System& system_)
+ : ServiceFramework{system_, "DynamicRightsInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "RequestApplicationRightsOnServer"},
+ {1, nullptr, "RequestAssignRights"},
+ {4, nullptr, "DeprecatedRequestAssignRightsToResume"},
+ {5, D<&IDynamicRightsInterface::VerifyActivatedRightsOwners>, "VerifyActivatedRightsOwners"},
+ {6, nullptr, "DeprecatedGetApplicationRightsStatus"},
+ {7, nullptr, "RequestPrefetchForDynamicRights"},
+ {8, nullptr, "GetDynamicRightsState"},
+ {9, nullptr, "RequestApplicationRightsOnServerToResume"},
+ {10, nullptr, "RequestAssignRightsToResume"},
+ {11, nullptr, "GetActivatedRightsUsers"},
+ {12, nullptr, "GetApplicationRightsStatus"},
+ {13, D<&IDynamicRightsInterface::GetRunningApplicationStatus>, "GetRunningApplicationStatus"},
+ {14, nullptr, "SelectApplicationLicense"},
+ {15, nullptr, "RequestContentsAuthorizationToken"},
+ {16, nullptr, "QualifyUser"},
+ {17, nullptr, "QualifyUserWithProcessId"},
+ {18, D<&IDynamicRightsInterface::NotifyApplicationRightsCheckStart>, "NotifyApplicationRightsCheckStart"},
+ {19, nullptr, "UpdateUserList"},
+ {20, nullptr, "IsRightsLostUser"},
+ {21, nullptr, "SetRequiredAddOnContentsOnContentsAvailabilityTransition"},
+ {22, nullptr, "GetLimitedApplicationLicense"},
+ {23, nullptr, "GetLimitedApplicationLicenseUpgradableEvent"},
+ {24, nullptr, "NotifyLimitedApplicationLicenseUpgradableEventForDebug"},
+ {25, nullptr, "RequestProceedDynamicRightsState"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IDynamicRightsInterface::~IDynamicRightsInterface() = default;
+
+Result IDynamicRightsInterface::NotifyApplicationRightsCheckStart() {
+ LOG_WARNING(Service_NS, "(STUBBED) called");
+ R_SUCCEED();
+}
+
+Result IDynamicRightsInterface::GetRunningApplicationStatus(Out<u32> out_status,
+ u64 rights_handle) {
+ LOG_WARNING(Service_NS, "(STUBBED) called, rights_handle={:#x}", rights_handle);
+ *out_status = 0;
+ R_SUCCEED();
+}
+
+Result IDynamicRightsInterface::VerifyActivatedRightsOwners(u64 rights_handle) {
+ LOG_WARNING(Service_NS, "(STUBBED) called, rights_handle={:#x}", rights_handle);
+ R_SUCCEED();
+}
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/dynamic_rights_interface.h b/src/core/hle/service/ns/dynamic_rights_interface.h
new file mode 100644
index 000000000..877e009b0
--- /dev/null
+++ b/src/core/hle/service/ns/dynamic_rights_interface.h
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+class IDynamicRightsInterface final : public ServiceFramework<IDynamicRightsInterface> {
+public:
+ explicit IDynamicRightsInterface(Core::System& system_);
+ ~IDynamicRightsInterface() override;
+
+private:
+ Result NotifyApplicationRightsCheckStart();
+ Result GetRunningApplicationStatus(Out<u32> out_status, u64 rights_handle);
+ Result VerifyActivatedRightsOwners(u64 rights_handle);
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/ecommerce_interface.cpp b/src/core/hle/service/ns/ecommerce_interface.cpp
new file mode 100644
index 000000000..76fc425f0
--- /dev/null
+++ b/src/core/hle/service/ns/ecommerce_interface.cpp
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/ns/ecommerce_interface.h"
+
+namespace Service::NS {
+
+IECommerceInterface::IECommerceInterface(Core::System& system_)
+ : ServiceFramework{system_, "IECommerceInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "RequestLinkDevice"},
+ {1, nullptr, "RequestCleanupAllPreInstalledApplications"},
+ {2, nullptr, "RequestCleanupPreInstalledApplication"},
+ {3, nullptr, "RequestSyncRights"},
+ {4, nullptr, "RequestUnlinkDevice"},
+ {5, nullptr, "RequestRevokeAllELicense"},
+ {6, nullptr, "RequestSyncRightsBasedOnAssignedELicenses"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IECommerceInterface::~IECommerceInterface() = default;
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/ecommerce_interface.h b/src/core/hle/service/ns/ecommerce_interface.h
new file mode 100644
index 000000000..4352101f4
--- /dev/null
+++ b/src/core/hle/service/ns/ecommerce_interface.h
@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+class IECommerceInterface final : public ServiceFramework<IECommerceInterface> {
+public:
+ explicit IECommerceInterface(Core::System& system_);
+ ~IECommerceInterface() override;
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/factory_reset_interface.cpp b/src/core/hle/service/ns/factory_reset_interface.cpp
new file mode 100644
index 000000000..fd5cf7e1f
--- /dev/null
+++ b/src/core/hle/service/ns/factory_reset_interface.cpp
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/ns/factory_reset_interface.h"
+
+namespace Service::NS {
+
+IFactoryResetInterface::IFactoryResetInterface(Core::System& system_)
+ : ServiceFramework{system_, "IFactoryResetInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {100, nullptr, "ResetToFactorySettings"},
+ {101, nullptr, "ResetToFactorySettingsWithoutUserSaveData"},
+ {102, nullptr, "ResetToFactorySettingsForRefurbishment"},
+ {103, nullptr, "ResetToFactorySettingsWithPlatformRegion"},
+ {104, nullptr, "ResetToFactorySettingsWithPlatformRegionAuthentication"},
+ {105, nullptr, "RequestResetToFactorySettingsSecurely"},
+ {106, nullptr, "RequestResetToFactorySettingsWithPlatformRegionAuthenticationSecurely"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IFactoryResetInterface::~IFactoryResetInterface() = default;
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/factory_reset_interface.h b/src/core/hle/service/ns/factory_reset_interface.h
new file mode 100644
index 000000000..50d125123
--- /dev/null
+++ b/src/core/hle/service/ns/factory_reset_interface.h
@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+class IFactoryResetInterface final : public ServiceFramework<IFactoryResetInterface> {
+public:
+ explicit IFactoryResetInterface(Core::System& system_);
+ ~IFactoryResetInterface() override;
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 19c3ff01b..8402e83cb 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -1,893 +1,38 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "common/logging/log.h"
-#include "common/settings.h"
-#include "core/arm/debug.h"
-#include "core/core.h"
-#include "core/file_sys/control_metadata.h"
-#include "core/file_sys/patch_manager.h"
-#include "core/file_sys/vfs/vfs.h"
-#include "core/hle/service/filesystem/filesystem.h"
-#include "core/hle/service/glue/glue_manager.h"
-#include "core/hle/service/ipc_helpers.h"
-#include "core/hle/service/ns/errors.h"
-#include "core/hle/service/ns/iplatform_service_manager.h"
-#include "core/hle/service/ns/language.h"
+#include "core/hle/service/ns/develop_interface.h"
#include "core/hle/service/ns/ns.h"
-#include "core/hle/service/ns/pdm_qry.h"
+#include "core/hle/service/ns/platform_service_manager.h"
+#include "core/hle/service/ns/query_service.h"
+#include "core/hle/service/ns/service_getter_interface.h"
+#include "core/hle/service/ns/system_update_interface.h"
+#include "core/hle/service/ns/vulnerability_manager_interface.h"
#include "core/hle/service/server_manager.h"
-#include "core/hle/service/set/settings_server.h"
namespace Service::NS {
-IAccountProxyInterface::IAccountProxyInterface(Core::System& system_)
- : ServiceFramework{system_, "IAccountProxyInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "CreateUserAccount"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-}
-
-IAccountProxyInterface::~IAccountProxyInterface() = default;
-
-IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_)
- : ServiceFramework{system_, "IApplicationManagerInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "ListApplicationRecord"},
- {1, nullptr, "GenerateApplicationRecordCount"},
- {2, nullptr, "GetApplicationRecordUpdateSystemEvent"},
- {3, nullptr, "GetApplicationViewDeprecated"},
- {4, nullptr, "DeleteApplicationEntity"},
- {5, nullptr, "DeleteApplicationCompletely"},
- {6, nullptr, "IsAnyApplicationEntityRedundant"},
- {7, nullptr, "DeleteRedundantApplicationEntity"},
- {8, nullptr, "IsApplicationEntityMovable"},
- {9, nullptr, "MoveApplicationEntity"},
- {11, nullptr, "CalculateApplicationOccupiedSize"},
- {16, nullptr, "PushApplicationRecord"},
- {17, nullptr, "ListApplicationRecordContentMeta"},
- {19, nullptr, "LaunchApplicationOld"},
- {21, nullptr, "GetApplicationContentPath"},
- {22, nullptr, "TerminateApplication"},
- {23, nullptr, "ResolveApplicationContentPath"},
- {26, nullptr, "BeginInstallApplication"},
- {27, nullptr, "DeleteApplicationRecord"},
- {30, nullptr, "RequestApplicationUpdateInfo"},
- {31, nullptr, "Unknown31"},
- {32, nullptr, "CancelApplicationDownload"},
- {33, nullptr, "ResumeApplicationDownload"},
- {35, nullptr, "UpdateVersionList"},
- {36, nullptr, "PushLaunchVersion"},
- {37, nullptr, "ListRequiredVersion"},
- {38, nullptr, "CheckApplicationLaunchVersion"},
- {39, nullptr, "CheckApplicationLaunchRights"},
- {40, nullptr, "GetApplicationLogoData"},
- {41, nullptr, "CalculateApplicationDownloadRequiredSize"},
- {42, nullptr, "CleanupSdCard"},
- {43, nullptr, "CheckSdCardMountStatus"},
- {44, nullptr, "GetSdCardMountStatusChangedEvent"},
- {45, nullptr, "GetGameCardAttachmentEvent"},
- {46, nullptr, "GetGameCardAttachmentInfo"},
- {47, nullptr, "GetTotalSpaceSize"},
- {48, nullptr, "GetFreeSpaceSize"},
- {49, nullptr, "GetSdCardRemovedEvent"},
- {52, nullptr, "GetGameCardUpdateDetectionEvent"},
- {53, nullptr, "DisableApplicationAutoDelete"},
- {54, nullptr, "EnableApplicationAutoDelete"},
- {55, &IApplicationManagerInterface::GetApplicationDesiredLanguage, "GetApplicationDesiredLanguage"},
- {56, nullptr, "SetApplicationTerminateResult"},
- {57, nullptr, "ClearApplicationTerminateResult"},
- {58, nullptr, "GetLastSdCardMountUnexpectedResult"},
- {59, &IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode, "ConvertApplicationLanguageToLanguageCode"},
- {60, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
- {61, nullptr, "GetBackgroundDownloadStressTaskInfo"},
- {62, nullptr, "GetGameCardStopper"},
- {63, nullptr, "IsSystemProgramInstalled"},
- {64, nullptr, "StartApplyDeltaTask"},
- {65, nullptr, "GetRequestServerStopper"},
- {66, nullptr, "GetBackgroundApplyDeltaStressTaskInfo"},
- {67, nullptr, "CancelApplicationApplyDelta"},
- {68, nullptr, "ResumeApplicationApplyDelta"},
- {69, nullptr, "CalculateApplicationApplyDeltaRequiredSize"},
- {70, nullptr, "ResumeAll"},
- {71, nullptr, "GetStorageSize"},
- {80, nullptr, "RequestDownloadApplication"},
- {81, nullptr, "RequestDownloadAddOnContent"},
- {82, nullptr, "DownloadApplication"},
- {83, nullptr, "CheckApplicationResumeRights"},
- {84, nullptr, "GetDynamicCommitEvent"},
- {85, nullptr, "RequestUpdateApplication2"},
- {86, nullptr, "EnableApplicationCrashReport"},
- {87, nullptr, "IsApplicationCrashReportEnabled"},
- {90, nullptr, "BoostSystemMemoryResourceLimit"},
- {91, nullptr, "DeprecatedLaunchApplication"},
- {92, nullptr, "GetRunningApplicationProgramId"},
- {93, nullptr, "GetMainApplicationProgramIndex"},
- {94, nullptr, "LaunchApplication"},
- {95, nullptr, "GetApplicationLaunchInfo"},
- {96, nullptr, "AcquireApplicationLaunchInfo"},
- {97, nullptr, "GetMainApplicationProgramIndexByApplicationLaunchInfo"},
- {98, nullptr, "EnableApplicationAllThreadDumpOnCrash"},
- {99, nullptr, "LaunchDevMenu"},
- {100, nullptr, "ResetToFactorySettings"},
- {101, nullptr, "ResetToFactorySettingsWithoutUserSaveData"},
- {102, nullptr, "ResetToFactorySettingsForRefurbishment"},
- {103, nullptr, "ResetToFactorySettingsWithPlatformRegion"},
- {104, nullptr, "ResetToFactorySettingsWithPlatformRegionAuthentication"},
- {105, nullptr, "RequestResetToFactorySettingsSecurely"},
- {106, nullptr, "RequestResetToFactorySettingsWithPlatformRegionAuthenticationSecurely"},
- {200, nullptr, "CalculateUserSaveDataStatistics"},
- {201, nullptr, "DeleteUserSaveDataAll"},
- {210, nullptr, "DeleteUserSystemSaveData"},
- {211, nullptr, "DeleteSaveData"},
- {220, nullptr, "UnregisterNetworkServiceAccount"},
- {221, nullptr, "UnregisterNetworkServiceAccountWithUserSaveDataDeletion"},
- {300, nullptr, "GetApplicationShellEvent"},
- {301, nullptr, "PopApplicationShellEventInfo"},
- {302, nullptr, "LaunchLibraryApplet"},
- {303, nullptr, "TerminateLibraryApplet"},
- {304, nullptr, "LaunchSystemApplet"},
- {305, nullptr, "TerminateSystemApplet"},
- {306, nullptr, "LaunchOverlayApplet"},
- {307, nullptr, "TerminateOverlayApplet"},
- {400, &IApplicationManagerInterface::GetApplicationControlData, "GetApplicationControlData"},
- {401, nullptr, "InvalidateAllApplicationControlCache"},
- {402, nullptr, "RequestDownloadApplicationControlData"},
- {403, nullptr, "GetMaxApplicationControlCacheCount"},
- {404, nullptr, "InvalidateApplicationControlCache"},
- {405, nullptr, "ListApplicationControlCacheEntryInfo"},
- {406, nullptr, "GetApplicationControlProperty"},
- {407, nullptr, "ListApplicationTitle"},
- {408, nullptr, "ListApplicationIcon"},
- {502, nullptr, "RequestCheckGameCardRegistration"},
- {503, nullptr, "RequestGameCardRegistrationGoldPoint"},
- {504, nullptr, "RequestRegisterGameCard"},
- {505, nullptr, "GetGameCardMountFailureEvent"},
- {506, nullptr, "IsGameCardInserted"},
- {507, nullptr, "EnsureGameCardAccess"},
- {508, nullptr, "GetLastGameCardMountFailureResult"},
- {509, nullptr, "ListApplicationIdOnGameCard"},
- {510, nullptr, "GetGameCardPlatformRegion"},
- {600, nullptr, "CountApplicationContentMeta"},
- {601, nullptr, "ListApplicationContentMetaStatus"},
- {602, nullptr, "ListAvailableAddOnContent"},
- {603, nullptr, "GetOwnedApplicationContentMetaStatus"},
- {604, nullptr, "RegisterContentsExternalKey"},
- {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
- {606, nullptr, "GetContentMetaStorage"},
- {607, nullptr, "ListAvailableAddOnContent"},
- {609, nullptr, "ListAvailabilityAssuredAddOnContent"},
- {610, nullptr, "GetInstalledContentMetaStorage"},
- {611, nullptr, "PrepareAddOnContent"},
- {700, nullptr, "PushDownloadTaskList"},
- {701, nullptr, "ClearTaskStatusList"},
- {702, nullptr, "RequestDownloadTaskList"},
- {703, nullptr, "RequestEnsureDownloadTask"},
- {704, nullptr, "ListDownloadTaskStatus"},
- {705, nullptr, "RequestDownloadTaskListData"},
- {800, nullptr, "RequestVersionList"},
- {801, nullptr, "ListVersionList"},
- {802, nullptr, "RequestVersionListData"},
- {900, nullptr, "GetApplicationRecord"},
- {901, nullptr, "GetApplicationRecordProperty"},
- {902, nullptr, "EnableApplicationAutoUpdate"},
- {903, nullptr, "DisableApplicationAutoUpdate"},
- {904, nullptr, "TouchApplication"},
- {905, nullptr, "RequestApplicationUpdate"},
- {906, nullptr, "IsApplicationUpdateRequested"},
- {907, nullptr, "WithdrawApplicationUpdateRequest"},
- {908, nullptr, "ListApplicationRecordInstalledContentMeta"},
- {909, nullptr, "WithdrawCleanupAddOnContentsWithNoRightsRecommendation"},
- {910, nullptr, "HasApplicationRecord"},
- {911, nullptr, "SetPreInstalledApplication"},
- {912, nullptr, "ClearPreInstalledApplicationFlag"},
- {913, nullptr, "ListAllApplicationRecord"},
- {914, nullptr, "HideApplicationRecord"},
- {915, nullptr, "ShowApplicationRecord"},
- {916, nullptr, "IsApplicationAutoDeleteDisabled"},
- {1000, nullptr, "RequestVerifyApplicationDeprecated"},
- {1001, nullptr, "CorruptApplicationForDebug"},
- {1002, nullptr, "RequestVerifyAddOnContentsRights"},
- {1003, nullptr, "RequestVerifyApplication"},
- {1004, nullptr, "CorruptContentForDebug"},
- {1200, nullptr, "NeedsUpdateVulnerability"},
- {1300, nullptr, "IsAnyApplicationEntityInstalled"},
- {1301, nullptr, "DeleteApplicationContentEntities"},
- {1302, nullptr, "CleanupUnrecordedApplicationEntity"},
- {1303, nullptr, "CleanupAddOnContentsWithNoRights"},
- {1304, nullptr, "DeleteApplicationContentEntity"},
- {1305, nullptr, "TryDeleteRunningApplicationEntity"},
- {1306, nullptr, "TryDeleteRunningApplicationCompletely"},
- {1307, nullptr, "TryDeleteRunningApplicationContentEntities"},
- {1308, nullptr, "DeleteApplicationCompletelyForDebug"},
- {1309, nullptr, "CleanupUnavailableAddOnContents"},
- {1310, nullptr, "RequestMoveApplicationEntity"},
- {1311, nullptr, "EstimateSizeToMove"},
- {1312, nullptr, "HasMovableEntity"},
- {1313, nullptr, "CleanupOrphanContents"},
- {1314, nullptr, "CheckPreconditionSatisfiedToMove"},
- {1400, nullptr, "PrepareShutdown"},
- {1500, nullptr, "FormatSdCard"},
- {1501, nullptr, "NeedsSystemUpdateToFormatSdCard"},
- {1502, nullptr, "GetLastSdCardFormatUnexpectedResult"},
- {1504, nullptr, "InsertSdCard"},
- {1505, nullptr, "RemoveSdCard"},
- {1506, nullptr, "GetSdCardStartupStatus"},
- {1600, nullptr, "GetSystemSeedForPseudoDeviceId"},
- {1601, nullptr, "ResetSystemSeedForPseudoDeviceId"},
- {1700, nullptr, "ListApplicationDownloadingContentMeta"},
- {1701, nullptr, "GetApplicationView"},
- {1702, nullptr, "GetApplicationDownloadTaskStatus"},
- {1703, nullptr, "GetApplicationViewDownloadErrorContext"},
- {1704, nullptr, "GetApplicationViewWithPromotionInfo"},
- {1705, nullptr, "IsPatchAutoDeletableApplication"},
- {1800, nullptr, "IsNotificationSetupCompleted"},
- {1801, nullptr, "GetLastNotificationInfoCount"},
- {1802, nullptr, "ListLastNotificationInfo"},
- {1803, nullptr, "ListNotificationTask"},
- {1900, nullptr, "IsActiveAccount"},
- {1901, nullptr, "RequestDownloadApplicationPrepurchasedRights"},
- {1902, nullptr, "GetApplicationTicketInfo"},
- {1903, nullptr, "RequestDownloadApplicationPrepurchasedRightsForAccount"},
- {2000, nullptr, "GetSystemDeliveryInfo"},
- {2001, nullptr, "SelectLatestSystemDeliveryInfo"},
- {2002, nullptr, "VerifyDeliveryProtocolVersion"},
- {2003, nullptr, "GetApplicationDeliveryInfo"},
- {2004, nullptr, "HasAllContentsToDeliver"},
- {2005, nullptr, "CompareApplicationDeliveryInfo"},
- {2006, nullptr, "CanDeliverApplication"},
- {2007, nullptr, "ListContentMetaKeyToDeliverApplication"},
- {2008, nullptr, "NeedsSystemUpdateToDeliverApplication"},
- {2009, nullptr, "EstimateRequiredSize"},
- {2010, nullptr, "RequestReceiveApplication"},
- {2011, nullptr, "CommitReceiveApplication"},
- {2012, nullptr, "GetReceiveApplicationProgress"},
- {2013, nullptr, "RequestSendApplication"},
- {2014, nullptr, "GetSendApplicationProgress"},
- {2015, nullptr, "CompareSystemDeliveryInfo"},
- {2016, nullptr, "ListNotCommittedContentMeta"},
- {2017, nullptr, "CreateDownloadTask"},
- {2018, nullptr, "GetApplicationDeliveryInfoHash"},
- {2050, nullptr, "GetApplicationRightsOnClient"},
- {2051, nullptr, "InvalidateRightsIdCache"},
- {2100, nullptr, "GetApplicationTerminateResult"},
- {2101, nullptr, "GetRawApplicationTerminateResult"},
- {2150, nullptr, "CreateRightsEnvironment"},
- {2151, nullptr, "DestroyRightsEnvironment"},
- {2152, nullptr, "ActivateRightsEnvironment"},
- {2153, nullptr, "DeactivateRightsEnvironment"},
- {2154, nullptr, "ForceActivateRightsContextForExit"},
- {2155, nullptr, "UpdateRightsEnvironmentStatus"},
- {2156, nullptr, "CreateRightsEnvironmentForMicroApplication"},
- {2160, nullptr, "AddTargetApplicationToRightsEnvironment"},
- {2161, nullptr, "SetUsersToRightsEnvironment"},
- {2170, nullptr, "GetRightsEnvironmentStatus"},
- {2171, nullptr, "GetRightsEnvironmentStatusChangedEvent"},
- {2180, nullptr, "RequestExtendRightsInRightsEnvironment"},
- {2181, nullptr, "GetResultOfExtendRightsInRightsEnvironment"},
- {2182, nullptr, "SetActiveRightsContextUsingStateToRightsEnvironment"},
- {2190, nullptr, "GetRightsEnvironmentHandleForApplication"},
- {2199, nullptr, "GetRightsEnvironmentCountForDebug"},
- {2200, nullptr, "GetGameCardApplicationCopyIdentifier"},
- {2201, nullptr, "GetInstalledApplicationCopyIdentifier"},
- {2250, nullptr, "RequestReportActiveELicence"},
- {2300, nullptr, "ListEventLog"},
- {2350, nullptr, "PerformAutoUpdateByApplicationId"},
- {2351, nullptr, "RequestNoDownloadRightsErrorResolution"},
- {2352, nullptr, "RequestResolveNoDownloadRightsError"},
- {2353, nullptr, "GetApplicationDownloadTaskInfo"},
- {2354, nullptr, "PrioritizeApplicationBackgroundTask"},
- {2355, nullptr, "PreferStorageEfficientUpdate"},
- {2356, nullptr, "RequestStorageEfficientUpdatePreferable"},
- {2357, nullptr, "EnableMultiCoreDownload"},
- {2358, nullptr, "DisableMultiCoreDownload"},
- {2359, nullptr, "IsMultiCoreDownloadEnabled"},
- {2400, nullptr, "GetPromotionInfo"},
- {2401, nullptr, "CountPromotionInfo"},
- {2402, nullptr, "ListPromotionInfo"},
- {2403, nullptr, "ImportPromotionJsonForDebug"},
- {2404, nullptr, "ClearPromotionInfoForDebug"},
- {2500, nullptr, "ConfirmAvailableTime"},
- {2510, nullptr, "CreateApplicationResource"},
- {2511, nullptr, "GetApplicationResource"},
- {2513, nullptr, "LaunchMicroApplication"},
- {2514, nullptr, "ClearTaskOfAsyncTaskManager"},
- {2515, nullptr, "CleanupAllPlaceHolderAndFragmentsIfNoTask"},
- {2516, nullptr, "EnsureApplicationCertificate"},
- {2517, nullptr, "CreateApplicationInstance"},
- {2518, nullptr, "UpdateQualificationForDebug"},
- {2519, nullptr, "IsQualificationTransitionSupported"},
- {2520, nullptr, "IsQualificationTransitionSupportedByProcessId"},
- {2521, nullptr, "GetRightsUserChangedEvent"},
- {2522, nullptr, "IsRomRedirectionAvailable"},
- {2800, nullptr, "GetApplicationIdOfPreomia"},
- {3000, nullptr, "RegisterDeviceLockKey"},
- {3001, nullptr, "UnregisterDeviceLockKey"},
- {3002, nullptr, "VerifyDeviceLockKey"},
- {3003, nullptr, "HideApplicationIcon"},
- {3004, nullptr, "ShowApplicationIcon"},
- {3005, nullptr, "HideApplicationTitle"},
- {3006, nullptr, "ShowApplicationTitle"},
- {3007, nullptr, "EnableGameCard"},
- {3008, nullptr, "DisableGameCard"},
- {3009, nullptr, "EnableLocalContentShare"},
- {3010, nullptr, "DisableLocalContentShare"},
- {3011, nullptr, "IsApplicationIconHidden"},
- {3012, nullptr, "IsApplicationTitleHidden"},
- {3013, nullptr, "IsGameCardEnabled"},
- {3014, nullptr, "IsLocalContentShareEnabled"},
- {3050, nullptr, "ListAssignELicenseTaskResult"},
- {9999, nullptr, "GetApplicationCertificate"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-}
-
-IApplicationManagerInterface::~IApplicationManagerInterface() = default;
-
-void IApplicationManagerInterface::GetApplicationControlData(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto flag = rp.PopRaw<u64>();
- LOG_DEBUG(Service_NS, "called with flag={:016X}", flag);
-
- const auto title_id = rp.PopRaw<u64>();
-
- const auto size = ctx.GetWriteBufferSize();
-
- const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
- system.GetContentProvider()};
- const auto control = pm.GetControlMetadata();
-
- std::vector<u8> out;
-
- if (control.first != nullptr) {
- if (size < 0x4000) {
- LOG_ERROR(Service_NS,
- "output buffer is too small! (actual={:016X}, expected_min=0x4000)", size);
- IPC::ResponseBuilder rb{ctx, 2};
- // TODO(DarkLordZach): Find a better error code for this.
- rb.Push(ResultUnknown);
- return;
- }
-
- out.resize(0x4000);
- const auto bytes = control.first->GetRawBytes();
- std::memcpy(out.data(), bytes.data(), bytes.size());
- } else {
- LOG_WARNING(Service_NS, "missing NACP data for title_id={:016X}, defaulting to zeros.",
- title_id);
- out.resize(std::min<u64>(0x4000, size));
- }
-
- if (control.second != nullptr) {
- if (size < 0x4000 + control.second->GetSize()) {
- LOG_ERROR(Service_NS,
- "output buffer is too small! (actual={:016X}, expected_min={:016X})", size,
- 0x4000 + control.second->GetSize());
- IPC::ResponseBuilder rb{ctx, 2};
- // TODO(DarkLordZach): Find a better error code for this.
- rb.Push(ResultUnknown);
- return;
- }
-
- out.resize(0x4000 + control.second->GetSize());
- control.second->Read(out.data() + 0x4000, control.second->GetSize());
- } else {
- LOG_WARNING(Service_NS, "missing icon data for title_id={:016X}, defaulting to zeros.",
- title_id);
- }
-
- ctx.WriteBuffer(out);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(static_cast<u32>(out.size()));
-}
-
-void IApplicationManagerInterface::GetApplicationDesiredLanguage(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto supported_languages = rp.Pop<u32>();
-
- u8 desired_language{};
- const auto res = GetApplicationDesiredLanguage(&desired_language, supported_languages);
- if (res == ResultSuccess) {
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(desired_language);
- } else {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res);
- }
-}
-
-Result IApplicationManagerInterface::GetApplicationDesiredLanguage(u8* out_desired_language,
- const u32 supported_languages) {
- LOG_DEBUG(Service_NS, "called with supported_languages={:08X}", supported_languages);
-
- // Get language code from settings
- const auto language_code =
- Set::GetLanguageCodeFromIndex(static_cast<s32>(Settings::values.language_index.GetValue()));
-
- // Convert to application language, get priority list
- const auto application_language = ConvertToApplicationLanguage(language_code);
- if (application_language == std::nullopt) {
- LOG_ERROR(Service_NS, "Could not convert application language! language_code={}",
- language_code);
- return Service::NS::ResultApplicationLanguageNotFound;
- }
- const auto priority_list = GetApplicationLanguagePriorityList(*application_language);
- if (!priority_list) {
- LOG_ERROR(Service_NS,
- "Could not find application language priorities! application_language={}",
- *application_language);
- return Service::NS::ResultApplicationLanguageNotFound;
- }
-
- // Try to find a valid language.
- for (const auto lang : *priority_list) {
- const auto supported_flag = GetSupportedLanguageFlag(lang);
- if (supported_languages == 0 || (supported_languages & supported_flag) == supported_flag) {
- *out_desired_language = static_cast<u8>(lang);
- return ResultSuccess;
- }
- }
-
- LOG_ERROR(Service_NS, "Could not find a valid language! supported_languages={:08X}",
- supported_languages);
- return Service::NS::ResultApplicationLanguageNotFound;
-}
-
-void IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode(
- HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto application_language = rp.Pop<u8>();
-
- u64 language_code{};
- const auto res = ConvertApplicationLanguageToLanguageCode(&language_code, application_language);
- if (res == ResultSuccess) {
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.Push(language_code);
- } else {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res);
- }
-}
-
-Result IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode(
- u64* out_language_code, u8 application_language) {
- const auto language_code =
- ConvertToLanguageCode(static_cast<ApplicationLanguage>(application_language));
- if (language_code == std::nullopt) {
- LOG_ERROR(Service_NS, "Language not found! application_language={}", application_language);
- return Service::NS::ResultApplicationLanguageNotFound;
- }
-
- *out_language_code = static_cast<u64>(*language_code);
- return ResultSuccess;
-}
-
-IApplicationVersionInterface::IApplicationVersionInterface(Core::System& system_)
- : ServiceFramework{system_, "IApplicationVersionInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "GetLaunchRequiredVersion"},
- {1, nullptr, "UpgradeLaunchRequiredVersion"},
- {35, nullptr, "UpdateVersionList"},
- {36, nullptr, "PushLaunchVersion"},
- {37, nullptr, "ListRequiredVersion"},
- {800, nullptr, "RequestVersionList"},
- {801, nullptr, "ListVersionList"},
- {802, nullptr, "RequestVersionListData"},
- {900, nullptr, "ImportAutoUpdatePolicyJsonForDebug"},
- {901, nullptr, "ListDefaultAutoUpdatePolicy"},
- {902, nullptr, "ListAutoUpdatePolicyForSpecificApplication"},
- {1000, nullptr, "PerformAutoUpdate"},
- {1001, nullptr, "ListAutoUpdateSchedule"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-}
-
-IApplicationVersionInterface::~IApplicationVersionInterface() = default;
-
-IContentManagementInterface::IContentManagementInterface(Core::System& system_)
- : ServiceFramework{system_, "IContentManagementInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {11, nullptr, "CalculateApplicationOccupiedSize"},
- {43, nullptr, "CheckSdCardMountStatus"},
- {47, &IContentManagementInterface::GetTotalSpaceSize, "GetTotalSpaceSize"},
- {48, &IContentManagementInterface::GetFreeSpaceSize, "GetFreeSpaceSize"},
- {600, nullptr, "CountApplicationContentMeta"},
- {601, nullptr, "ListApplicationContentMetaStatus"},
- {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
- {607, nullptr, "IsAnyApplicationRunning"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-}
-
-IContentManagementInterface::~IContentManagementInterface() = default;
-
-void IContentManagementInterface::GetTotalSpaceSize(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto storage{rp.PopEnum<FileSys::StorageId>()};
-
- LOG_INFO(Service_Capture, "called, storage={}", storage);
-
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.Push<u64>(system.GetFileSystemController().GetTotalSpaceSize(storage));
-}
-
-void IContentManagementInterface::GetFreeSpaceSize(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto storage{rp.PopEnum<FileSys::StorageId>()};
-
- LOG_INFO(Service_Capture, "called, storage={}", storage);
-
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.Push<u64>(system.GetFileSystemController().GetFreeSpaceSize(storage));
-}
-
-IDocumentInterface::IDocumentInterface(Core::System& system_)
- : ServiceFramework{system_, "IDocumentInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {21, nullptr, "GetApplicationContentPath"},
- {23, &IDocumentInterface::ResolveApplicationContentPath, "ResolveApplicationContentPath"},
- {92, &IDocumentInterface::GetRunningApplicationProgramId, "GetRunningApplicationProgramId"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-}
-
-IDocumentInterface::~IDocumentInterface() = default;
-
-void IDocumentInterface::ResolveApplicationContentPath(HLERequestContext& ctx) {
- struct ContentPath {
- u8 file_system_proxy_type;
- u64 program_id;
- };
- static_assert(sizeof(ContentPath) == 0x10, "ContentPath has wrong size");
-
- IPC::RequestParser rp{ctx};
- auto content_path = rp.PopRaw<ContentPath>();
- LOG_WARNING(Service_NS, "(STUBBED) called, file_system_proxy_type={}, program_id={:016X}",
- content_path.file_system_proxy_type, content_path.program_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void IDocumentInterface::GetRunningApplicationProgramId(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto caller_program_id = rp.PopRaw<u64>();
- LOG_WARNING(Service_NS, "(STUBBED) called, caller_program_id={:016X}", caller_program_id);
-
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.Push<u64>(system.GetApplicationProcessProgramID());
-}
-
-IDownloadTaskInterface::IDownloadTaskInterface(Core::System& system_)
- : ServiceFramework{system_, "IDownloadTaskInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {701, nullptr, "ClearTaskStatusList"},
- {702, nullptr, "RequestDownloadTaskList"},
- {703, nullptr, "RequestEnsureDownloadTask"},
- {704, nullptr, "ListDownloadTaskStatus"},
- {705, nullptr, "RequestDownloadTaskListData"},
- {706, nullptr, "TryCommitCurrentApplicationDownloadTask"},
- {707, nullptr, "EnableAutoCommit"},
- {708, nullptr, "DisableAutoCommit"},
- {709, nullptr, "TriggerDynamicCommitEvent"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-}
-
-IDownloadTaskInterface::~IDownloadTaskInterface() = default;
-
-IECommerceInterface::IECommerceInterface(Core::System& system_)
- : ServiceFramework{system_, "IECommerceInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "RequestLinkDevice"},
- {1, nullptr, "RequestCleanupAllPreInstalledApplications"},
- {2, nullptr, "RequestCleanupPreInstalledApplication"},
- {3, nullptr, "RequestSyncRights"},
- {4, nullptr, "RequestUnlinkDevice"},
- {5, nullptr, "RequestRevokeAllELicense"},
- {6, nullptr, "RequestSyncRightsBasedOnAssignedELicenses"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-}
-
-IECommerceInterface::~IECommerceInterface() = default;
-
-IFactoryResetInterface::IFactoryResetInterface(Core::System& system_)
- : ServiceFramework{system_, "IFactoryResetInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {100, nullptr, "ResetToFactorySettings"},
- {101, nullptr, "ResetToFactorySettingsWithoutUserSaveData"},
- {102, nullptr, "ResetToFactorySettingsForRefurbishment"},
- {103, nullptr, "ResetToFactorySettingsWithPlatformRegion"},
- {104, nullptr, "ResetToFactorySettingsWithPlatformRegionAuthentication"},
- {105, nullptr, "RequestResetToFactorySettingsSecurely"},
- {106, nullptr, "RequestResetToFactorySettingsWithPlatformRegionAuthenticationSecurely"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-}
-
-IFactoryResetInterface::~IFactoryResetInterface() = default;
-
-IReadOnlyApplicationRecordInterface::IReadOnlyApplicationRecordInterface(Core::System& system_)
- : ServiceFramework{system_, "IReadOnlyApplicationRecordInterface"} {
- static const FunctionInfo functions[] = {
- {0, &IReadOnlyApplicationRecordInterface::HasApplicationRecord, "HasApplicationRecord"},
- {1, nullptr, "NotifyApplicationFailure"},
- {2, &IReadOnlyApplicationRecordInterface::IsDataCorruptedResult, "IsDataCorruptedResult"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-}
-
-IReadOnlyApplicationRecordInterface::~IReadOnlyApplicationRecordInterface() = default;
-
-void IReadOnlyApplicationRecordInterface::HasApplicationRecord(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u64 program_id = rp.PopRaw<u64>();
- LOG_WARNING(Service_NS, "(STUBBED) called, program_id={:X}", program_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u8>(1);
-}
-
-void IReadOnlyApplicationRecordInterface::IsDataCorruptedResult(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto result = rp.PopRaw<Result>();
- LOG_WARNING(Service_NS, "(STUBBED) called, result={:#x}", result.GetInnerValue());
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u8>(0);
-}
-
-IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterface(
- Core::System& system_)
- : ServiceFramework{system_, "IReadOnlyApplicationControlDataInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &IReadOnlyApplicationControlDataInterface::GetApplicationControlData, "GetApplicationControlData"},
- {1, nullptr, "GetApplicationDesiredLanguage"},
- {2, nullptr, "ConvertApplicationLanguageToLanguageCode"},
- {3, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
- {4, nullptr, "SelectApplicationDesiredLanguage"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-}
-
-IReadOnlyApplicationControlDataInterface::~IReadOnlyApplicationControlDataInterface() = default;
-
-void IReadOnlyApplicationControlDataInterface::GetApplicationControlData(HLERequestContext& ctx) {
- enum class ApplicationControlSource : u8 {
- CacheOnly,
- Storage,
- StorageOnly,
- };
-
- struct RequestParameters {
- ApplicationControlSource source;
- u64 application_id;
- };
- static_assert(sizeof(RequestParameters) == 0x10, "RequestParameters has incorrect size.");
-
- IPC::RequestParser rp{ctx};
- std::vector<u8> nacp_data{};
- const auto parameters{rp.PopRaw<RequestParameters>()};
- const auto result =
- system.GetARPManager().GetControlProperty(&nacp_data, parameters.application_id);
-
- if (result == ResultSuccess) {
- ctx.WriteBuffer(nacp_data.data(), nacp_data.size());
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-NS::NS(const char* name, Core::System& system_) : ServiceFramework{system_, name} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {7988, nullptr, "GetDynamicRightsInterface"},
- {7989, &NS::PushInterface<IReadOnlyApplicationControlDataInterface>, "GetReadOnlyApplicationControlDataInterface"},
- {7991, &NS::PushInterface<IReadOnlyApplicationRecordInterface>, "GetReadOnlyApplicationRecordInterface"},
- {7992, &NS::PushInterface<IECommerceInterface>, "GetECommerceInterface"},
- {7993, &NS::PushInterface<IApplicationVersionInterface>, "GetApplicationVersionInterface"},
- {7994, &NS::PushInterface<IFactoryResetInterface>, "GetFactoryResetInterface"},
- {7995, &NS::PushInterface<IAccountProxyInterface>, "GetAccountProxyInterface"},
- {7996, &NS::PushIApplicationManagerInterface, "GetApplicationManagerInterface"},
- {7997, &NS::PushInterface<IDownloadTaskInterface>, "GetDownloadTaskInterface"},
- {7998, &NS::PushInterface<IContentManagementInterface>, "GetContentManagementInterface"},
- {7999, &NS::PushInterface<IDocumentInterface>, "GetDocumentInterface"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-}
-
-NS::~NS() = default;
-
-std::shared_ptr<IApplicationManagerInterface> NS::GetApplicationManagerInterface() const {
- return GetInterface<IApplicationManagerInterface>(system);
-}
-
-class NS_DEV final : public ServiceFramework<NS_DEV> {
-public:
- explicit NS_DEV(Core::System& system_) : ServiceFramework{system_, "ns:dev"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "LaunchProgram"},
- {1, nullptr, "TerminateProcess"},
- {2, nullptr, "TerminateProgram"},
- {4, nullptr, "GetShellEvent"},
- {5, nullptr, "GetShellEventInfo"},
- {6, nullptr, "TerminateApplication"},
- {7, nullptr, "PrepareLaunchProgramFromHost"},
- {8, nullptr, "LaunchApplicationFromHost"},
- {9, nullptr, "LaunchApplicationWithStorageIdForDevelop"},
- {10, nullptr, "IsSystemMemoryResourceLimitBoosted"},
- {11, nullptr, "GetRunningApplicationProcessIdForDevelop"},
- {12, nullptr, "SetCurrentApplicationRightsEnvironmentCanBeActiveForDevelop"},
- {13, nullptr, "CreateApplicationResourceForDevelop"},
- {14, nullptr, "IsPreomiaForDevelop"},
- {15, nullptr, "GetApplicationProgramIdFromHost"},
- {16, nullptr, "RefreshCachedDebugValues"},
- {17, nullptr, "PrepareLaunchApplicationFromHost"},
- {18, nullptr, "GetLaunchEvent"},
- {19, nullptr, "GetLaunchResult"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-};
-
-class ISystemUpdateControl final : public ServiceFramework<ISystemUpdateControl> {
-public:
- explicit ISystemUpdateControl(Core::System& system_)
- : ServiceFramework{system_, "ISystemUpdateControl"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "HasDownloaded"},
- {1, nullptr, "RequestCheckLatestUpdate"},
- {2, nullptr, "RequestDownloadLatestUpdate"},
- {3, nullptr, "GetDownloadProgress"},
- {4, nullptr, "ApplyDownloadedUpdate"},
- {5, nullptr, "RequestPrepareCardUpdate"},
- {6, nullptr, "GetPrepareCardUpdateProgress"},
- {7, nullptr, "HasPreparedCardUpdate"},
- {8, nullptr, "ApplyCardUpdate"},
- {9, nullptr, "GetDownloadedEulaDataSize"},
- {10, nullptr, "GetDownloadedEulaData"},
- {11, nullptr, "SetupCardUpdate"},
- {12, nullptr, "GetPreparedCardUpdateEulaDataSize"},
- {13, nullptr, "GetPreparedCardUpdateEulaData"},
- {14, nullptr, "SetupCardUpdateViaSystemUpdater"},
- {15, nullptr, "HasReceived"},
- {16, nullptr, "RequestReceiveSystemUpdate"},
- {17, nullptr, "GetReceiveProgress"},
- {18, nullptr, "ApplyReceivedUpdate"},
- {19, nullptr, "GetReceivedEulaDataSize"},
- {20, nullptr, "GetReceivedEulaData"},
- {21, nullptr, "SetupToReceiveSystemUpdate"},
- {22, nullptr, "RequestCheckLatestUpdateIncludesRebootlessUpdate"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-};
-
-class NS_SU final : public ServiceFramework<NS_SU> {
-public:
- explicit NS_SU(Core::System& system_) : ServiceFramework{system_, "ns:su"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "GetBackgroundNetworkUpdateState"},
- {1, &NS_SU::OpenSystemUpdateControl, "OpenSystemUpdateControl"},
- {2, nullptr, "NotifyExFatDriverRequired"},
- {3, nullptr, "ClearExFatDriverStatusForDebug"},
- {4, nullptr, "RequestBackgroundNetworkUpdate"},
- {5, nullptr, "NotifyBackgroundNetworkUpdate"},
- {6, nullptr, "NotifyExFatDriverDownloadedForDebug"},
- {9, nullptr, "GetSystemUpdateNotificationEventForContentDelivery"},
- {10, nullptr, "NotifySystemUpdateForContentDelivery"},
- {11, nullptr, "PrepareShutdown"},
- {12, nullptr, "Unknown12"},
- {13, nullptr, "Unknown13"},
- {14, nullptr, "Unknown14"},
- {15, nullptr, "Unknown15"},
- {16, nullptr, "DestroySystemUpdateTask"},
- {17, nullptr, "RequestSendSystemUpdate"},
- {18, nullptr, "GetSendSystemUpdateProgress"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
-private:
- void OpenSystemUpdateControl(HLERequestContext& ctx) {
- LOG_DEBUG(Service_NS, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<ISystemUpdateControl>(system);
- }
-};
-
-class NS_VM final : public ServiceFramework<NS_VM> {
-public:
- explicit NS_VM(Core::System& system_) : ServiceFramework{system_, "ns:vm"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {1200, &NS_VM::NeedsUpdateVulnerability, "NeedsUpdateVulnerability"},
- {1201, nullptr, "UpdateSafeSystemVersionForDebug"},
- {1202, nullptr, "GetSafeSystemVersion"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
-private:
- void NeedsUpdateVulnerability(HLERequestContext& ctx) {
- LOG_WARNING(Service_NS, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(false);
- }
-};
-
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
- server_manager->RegisterNamedService("ns:am2", std::make_shared<NS>("ns:am2", system));
- server_manager->RegisterNamedService("ns:ec", std::make_shared<NS>("ns:ec", system));
- server_manager->RegisterNamedService("ns:rid", std::make_shared<NS>("ns:rid", system));
- server_manager->RegisterNamedService("ns:rt", std::make_shared<NS>("ns:rt", system));
- server_manager->RegisterNamedService("ns:web", std::make_shared<NS>("ns:web", system));
- server_manager->RegisterNamedService("ns:ro", std::make_shared<NS>("ns:ro", system));
-
- server_manager->RegisterNamedService("ns:dev", std::make_shared<NS_DEV>(system));
- server_manager->RegisterNamedService("ns:su", std::make_shared<NS_SU>(system));
- server_manager->RegisterNamedService("ns:vm", std::make_shared<NS_VM>(system));
- server_manager->RegisterNamedService("pdm:qry", std::make_shared<PDM_QRY>(system));
+ server_manager->RegisterNamedService(
+ "ns:am2", std::make_shared<IServiceGetterInterface>(system, "ns:am2"));
+ server_manager->RegisterNamedService(
+ "ns:ec", std::make_shared<IServiceGetterInterface>(system, "ns:ec"));
+ server_manager->RegisterNamedService(
+ "ns:rid", std::make_shared<IServiceGetterInterface>(system, "ns:rid"));
+ server_manager->RegisterNamedService(
+ "ns:rt", std::make_shared<IServiceGetterInterface>(system, "ns:rt"));
+ server_manager->RegisterNamedService(
+ "ns:web", std::make_shared<IServiceGetterInterface>(system, "ns:web"));
+ server_manager->RegisterNamedService(
+ "ns:ro", std::make_shared<IServiceGetterInterface>(system, "ns:ro"));
+
+ server_manager->RegisterNamedService("ns:dev", std::make_shared<IDevelopInterface>(system));
+ server_manager->RegisterNamedService("ns:su", std::make_shared<ISystemUpdateInterface>(system));
+ server_manager->RegisterNamedService("ns:vm",
+ std::make_shared<IVulnerabilityManagerInterface>(system));
+ server_manager->RegisterNamedService("pdm:qry", std::make_shared<IQueryService>(system));
server_manager->RegisterNamedService("pl:s",
std::make_shared<IPlatformServiceManager>(system, "pl:s"));
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index 9ee306ef9..f79b4ae3d 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.h
@@ -3,141 +3,12 @@
#pragma once
-#include "core/hle/service/service.h"
-
namespace Core {
class System;
}
-namespace Service {
-
-namespace FileSystem {
-class FileSystemController;
-} // namespace FileSystem
-
-namespace NS {
-
-class IAccountProxyInterface final : public ServiceFramework<IAccountProxyInterface> {
-public:
- explicit IAccountProxyInterface(Core::System& system_);
- ~IAccountProxyInterface() override;
-};
-
-class IApplicationManagerInterface final : public ServiceFramework<IApplicationManagerInterface> {
-public:
- explicit IApplicationManagerInterface(Core::System& system_);
- ~IApplicationManagerInterface() override;
-
- Result GetApplicationDesiredLanguage(u8* out_desired_language, u32 supported_languages);
- Result ConvertApplicationLanguageToLanguageCode(u64* out_language_code,
- u8 application_language);
-
-private:
- void GetApplicationControlData(HLERequestContext& ctx);
- void GetApplicationDesiredLanguage(HLERequestContext& ctx);
- void ConvertApplicationLanguageToLanguageCode(HLERequestContext& ctx);
-};
-
-class IApplicationVersionInterface final : public ServiceFramework<IApplicationVersionInterface> {
-public:
- explicit IApplicationVersionInterface(Core::System& system_);
- ~IApplicationVersionInterface() override;
-};
-
-class IContentManagementInterface final : public ServiceFramework<IContentManagementInterface> {
-public:
- explicit IContentManagementInterface(Core::System& system_);
- ~IContentManagementInterface() override;
-
-private:
- void GetTotalSpaceSize(HLERequestContext& ctx);
- void GetFreeSpaceSize(HLERequestContext& ctx);
-};
-
-class IDocumentInterface final : public ServiceFramework<IDocumentInterface> {
-public:
- explicit IDocumentInterface(Core::System& system_);
- ~IDocumentInterface() override;
-
-private:
- void ResolveApplicationContentPath(HLERequestContext& ctx);
- void GetRunningApplicationProgramId(HLERequestContext& ctx);
-};
-
-class IDownloadTaskInterface final : public ServiceFramework<IDownloadTaskInterface> {
-public:
- explicit IDownloadTaskInterface(Core::System& system_);
- ~IDownloadTaskInterface() override;
-};
-
-class IECommerceInterface final : public ServiceFramework<IECommerceInterface> {
-public:
- explicit IECommerceInterface(Core::System& system_);
- ~IECommerceInterface() override;
-};
-
-class IFactoryResetInterface final : public ServiceFramework<IFactoryResetInterface> {
-public:
- explicit IFactoryResetInterface(Core::System& system_);
- ~IFactoryResetInterface() override;
-};
-
-class IReadOnlyApplicationRecordInterface final
- : public ServiceFramework<IReadOnlyApplicationRecordInterface> {
-public:
- explicit IReadOnlyApplicationRecordInterface(Core::System& system_);
- ~IReadOnlyApplicationRecordInterface() override;
-
-private:
- void HasApplicationRecord(HLERequestContext& ctx);
- void IsDataCorruptedResult(HLERequestContext& ctx);
-};
-
-class IReadOnlyApplicationControlDataInterface final
- : public ServiceFramework<IReadOnlyApplicationControlDataInterface> {
-public:
- explicit IReadOnlyApplicationControlDataInterface(Core::System& system_);
- ~IReadOnlyApplicationControlDataInterface() override;
-
-private:
- void GetApplicationControlData(HLERequestContext& ctx);
-};
-
-class NS final : public ServiceFramework<NS> {
-public:
- explicit NS(const char* name, Core::System& system_);
- ~NS() override;
-
- std::shared_ptr<IApplicationManagerInterface> GetApplicationManagerInterface() const;
-
-private:
- template <typename T, typename... Args>
- void PushInterface(HLERequestContext& ctx) {
- LOG_DEBUG(Service_NS, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<T>(system);
- }
-
- void PushIApplicationManagerInterface(HLERequestContext& ctx) {
- LOG_DEBUG(Service_NS, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IApplicationManagerInterface>(system);
- }
-
- template <typename T, typename... Args>
- std::shared_ptr<T> GetInterface(Args&&... args) const {
- static_assert(std::is_base_of_v<SessionRequestHandler, T>,
- "Not a base of ServiceFrameworkBase");
-
- return std::make_shared<T>(std::forward<Args>(args)...);
- }
-};
+namespace Service::NS {
void LoopProcess(Core::System& system);
-} // namespace NS
-} // namespace Service
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/errors.h b/src/core/hle/service/ns/ns_results.h
index 16d2ea6f7..16d2ea6f7 100644
--- a/src/core/hle/service/ns/errors.h
+++ b/src/core/hle/service/ns/ns_results.h
diff --git a/src/core/hle/service/ns/ns_types.h b/src/core/hle/service/ns/ns_types.h
new file mode 100644
index 000000000..38421b0f4
--- /dev/null
+++ b/src/core/hle/service/ns/ns_types.h
@@ -0,0 +1,111 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/uuid.h"
+#include "core/file_sys/romfs_factory.h"
+
+namespace Service::NS {
+
+enum class ApplicationRecordType : u8 {
+ Installing = 2,
+ Installed = 3,
+ GameCardNotInserted = 5,
+ Archived = 11,
+ GameCard = 16,
+};
+
+enum class ApplicationControlSource : u8 {
+ CacheOnly = 0,
+ Storage = 1,
+ StorageOnly = 2,
+};
+
+enum class BackgroundNetworkUpdateState : u8 {
+ None,
+ InProgress,
+ Ready,
+};
+
+struct ApplicationRecord {
+ u64 application_id;
+ ApplicationRecordType type;
+ u8 unknown;
+ INSERT_PADDING_BYTES_NOINIT(0x6);
+ u8 unknown2;
+ INSERT_PADDING_BYTES_NOINIT(0x7);
+};
+static_assert(sizeof(ApplicationRecord) == 0x18, "ApplicationRecord has incorrect size.");
+
+/// ApplicationView
+struct ApplicationView {
+ u64 application_id; ///< ApplicationId.
+ u32 unk; ///< Unknown.
+ u32 flags; ///< Flags.
+ std::array<u8, 0x10> unk_x10; ///< Unknown.
+ u32 unk_x20; ///< Unknown.
+ u16 unk_x24; ///< Unknown.
+ std::array<u8, 0x2> unk_x26; ///< Unknown.
+ std::array<u8, 0x8> unk_x28; ///< Unknown.
+ std::array<u8, 0x10> unk_x30; ///< Unknown.
+ u32 unk_x40; ///< Unknown.
+ u8 unk_x44; ///< Unknown.
+ std::array<u8, 0xb> unk_x45; ///< Unknown.
+};
+static_assert(sizeof(ApplicationView) == 0x50, "ApplicationView has incorrect size.");
+
+struct ApplicationRightsOnClient {
+ u64 application_id;
+ Common::UUID uid;
+ u8 flags;
+ u8 flags2;
+ INSERT_PADDING_BYTES_NOINIT(0x6);
+};
+static_assert(sizeof(ApplicationRightsOnClient) == 0x20,
+ "ApplicationRightsOnClient has incorrect size.");
+
+/// NsPromotionInfo
+struct PromotionInfo {
+ u64 start_timestamp; ///< POSIX timestamp for the promotion start.
+ u64 end_timestamp; ///< POSIX timestamp for the promotion end.
+ s64 remaining_time; ///< Remaining time until the promotion ends, in nanoseconds
+ ///< ({end_timestamp - current_time} converted to nanoseconds).
+ INSERT_PADDING_BYTES_NOINIT(0x4);
+ u8 flags; ///< Flags. Bit0: whether the PromotionInfo is valid (including bit1). Bit1 clear:
+ ///< remaining_time is set.
+ INSERT_PADDING_BYTES_NOINIT(0x3);
+};
+static_assert(sizeof(PromotionInfo) == 0x20, "PromotionInfo has incorrect size.");
+
+/// NsApplicationViewWithPromotionInfo
+struct ApplicationViewWithPromotionInfo {
+ ApplicationView view; ///< \ref NsApplicationView
+ PromotionInfo promotion; ///< \ref NsPromotionInfo
+};
+static_assert(sizeof(ApplicationViewWithPromotionInfo) == 0x70,
+ "ApplicationViewWithPromotionInfo has incorrect size.");
+
+struct ApplicationOccupiedSizeEntity {
+ FileSys::StorageId storage_id;
+ u64 app_size;
+ u64 patch_size;
+ u64 aoc_size;
+};
+static_assert(sizeof(ApplicationOccupiedSizeEntity) == 0x20,
+ "ApplicationOccupiedSizeEntity has incorrect size.");
+
+struct ApplicationOccupiedSize {
+ std::array<ApplicationOccupiedSizeEntity, 4> entities;
+};
+static_assert(sizeof(ApplicationOccupiedSize) == 0x80,
+ "ApplicationOccupiedSize has incorrect size.");
+
+struct ContentPath {
+ u8 file_system_proxy_type;
+ u64 program_id;
+};
+static_assert(sizeof(ContentPath) == 0x10, "ContentPath has incorrect size.");
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/pdm_qry.cpp b/src/core/hle/service/ns/pdm_qry.cpp
deleted file mode 100644
index ce0ee30e0..000000000
--- a/src/core/hle/service/ns/pdm_qry.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <memory>
-
-#include "common/logging/log.h"
-#include "common/uuid.h"
-#include "core/hle/service/ipc_helpers.h"
-#include "core/hle/service/ns/pdm_qry.h"
-#include "core/hle/service/service.h"
-
-namespace Service::NS {
-
-PDM_QRY::PDM_QRY(Core::System& system_) : ServiceFramework{system_, "pdm:qry"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "QueryAppletEvent"},
- {1, nullptr, "QueryPlayStatistics"},
- {2, nullptr, "QueryPlayStatisticsByUserAccountId"},
- {3, nullptr, "QueryPlayStatisticsByNetworkServiceAccountId"},
- {4, nullptr, "QueryPlayStatisticsByApplicationId"},
- {5, &PDM_QRY::QueryPlayStatisticsByApplicationIdAndUserAccountId, "QueryPlayStatisticsByApplicationIdAndUserAccountId"},
- {6, nullptr, "QueryPlayStatisticsByApplicationIdAndNetworkServiceAccountId"},
- {7, nullptr, "QueryLastPlayTimeV0"},
- {8, nullptr, "QueryPlayEvent"},
- {9, nullptr, "GetAvailablePlayEventRange"},
- {10, nullptr, "QueryAccountEvent"},
- {11, nullptr, "QueryAccountPlayEvent"},
- {12, nullptr, "GetAvailableAccountPlayEventRange"},
- {13, nullptr, "QueryApplicationPlayStatisticsForSystemV0"},
- {14, nullptr, "QueryRecentlyPlayedApplication"},
- {15, nullptr, "GetRecentlyPlayedApplicationUpdateEvent"},
- {16, nullptr, "QueryApplicationPlayStatisticsByUserAccountIdForSystemV0"},
- {17, nullptr, "QueryLastPlayTime"},
- {18, nullptr, "QueryApplicationPlayStatisticsForSystem"},
- {19, nullptr, "QueryApplicationPlayStatisticsByUserAccountIdForSystem"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-}
-
-PDM_QRY::~PDM_QRY() = default;
-
-void PDM_QRY::QueryPlayStatisticsByApplicationIdAndUserAccountId(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto unknown = rp.Pop<bool>();
- rp.Pop<u8>(); // Padding
- const auto application_id = rp.Pop<u64>();
- const auto user_account_uid = rp.PopRaw<Common::UUID>();
-
- // TODO(German77): Read statistics of the game
- PlayStatistics statistics{
- .application_id = application_id,
- .total_launches = 1,
- };
-
- LOG_WARNING(Service_NS,
- "(STUBBED) called. unknown={}. application_id=0x{:016X}, user_account_uid=0x{}",
- unknown, application_id, user_account_uid.RawString());
-
- IPC::ResponseBuilder rb{ctx, 12};
- rb.Push(ResultSuccess);
- rb.PushRaw(statistics);
-}
-
-} // namespace Service::NS
diff --git a/src/core/hle/service/ns/iplatform_service_manager.cpp b/src/core/hle/service/ns/platform_service_manager.cpp
index 46268be95..23cf05005 100644
--- a/src/core/hle/service/ns/iplatform_service_manager.cpp
+++ b/src/core/hle/service/ns/platform_service_manager.cpp
@@ -18,9 +18,9 @@
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_memory.h"
+#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/filesystem/filesystem.h"
-#include "core/hle/service/ipc_helpers.h"
-#include "core/hle/service/ns/iplatform_service_manager.h"
+#include "core/hle/service/ns/platform_service_manager.h"
namespace Service::NS {
@@ -37,11 +37,6 @@ constexpr u32 EXPECTED_MAGIC{0x36f81a1e}; // What we expect the encrypted bfttf
constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000};
constexpr FontRegion EMPTY_REGION{0, 0};
-enum class LoadState : u32 {
- Loading = 0,
- Done = 1,
-};
-
static void DecryptSharedFont(const std::vector<u32>& input, Kernel::PhysicalMemory& output,
std::size_t& offset) {
ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE,
@@ -138,13 +133,13 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch
: ServiceFramework{system_, service_name_}, impl{std::make_unique<Impl>()} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, &IPlatformServiceManager::RequestLoad, "RequestLoad"},
- {1, &IPlatformServiceManager::GetLoadState, "GetLoadState"},
- {2, &IPlatformServiceManager::GetSize, "GetSize"},
- {3, &IPlatformServiceManager::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"},
- {4, &IPlatformServiceManager::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"},
- {5, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"},
- {6, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriorityForSystem"},
+ {0, D<&IPlatformServiceManager::RequestLoad>, "RequestLoad"},
+ {1, D<&IPlatformServiceManager::GetLoadState>, "GetLoadState"},
+ {2, D<&IPlatformServiceManager::GetSize>, "GetSize"},
+ {3, D<&IPlatformServiceManager::GetSharedMemoryAddressOffset>, "GetSharedMemoryAddressOffset"},
+ {4, D<&IPlatformServiceManager::GetSharedMemoryNativeHandle>, "GetSharedMemoryNativeHandle"},
+ {5, D<&IPlatformServiceManager::GetSharedFontInOrderOfPriority>, "GetSharedFontInOrderOfPriority"},
+ {6, D<&IPlatformServiceManager::GetSharedFontInOrderOfPriority>, "GetSharedFontInOrderOfPriorityForSystem"},
{100, nullptr, "RequestApplicationFunctionAuthorization"},
{101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"},
{102, nullptr, "RequestApplicationFunctionAuthorizationByApplicationId"},
@@ -208,47 +203,33 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch
IPlatformServiceManager::~IPlatformServiceManager() = default;
-void IPlatformServiceManager::RequestLoad(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u32 shared_font_type{rp.Pop<u32>()};
+Result IPlatformServiceManager::RequestLoad(SharedFontType type) {
// Games don't call this so all fonts should be loaded
- LOG_DEBUG(Service_NS, "called, shared_font_type={}", shared_font_type);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ LOG_DEBUG(Service_NS, "called, shared_font_type={}", type);
+ R_SUCCEED();
}
-void IPlatformServiceManager::GetLoadState(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u32 font_id{rp.Pop<u32>()};
- LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(static_cast<u32>(LoadState::Done));
+Result IPlatformServiceManager::GetLoadState(Out<LoadState> out_load_state, SharedFontType type) {
+ LOG_DEBUG(Service_NS, "called, shared_font_type={}", type);
+ *out_load_state = LoadState::Loaded;
+ R_SUCCEED();
}
-void IPlatformServiceManager::GetSize(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u32 font_id{rp.Pop<u32>()};
- LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(impl->GetSharedFontRegion(font_id).size);
+Result IPlatformServiceManager::GetSize(Out<u32> out_size, SharedFontType type) {
+ LOG_DEBUG(Service_NS, "called, shared_font_type={}", type);
+ *out_size = impl->GetSharedFontRegion(static_cast<size_t>(type)).size;
+ R_SUCCEED();
}
-void IPlatformServiceManager::GetSharedMemoryAddressOffset(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u32 font_id{rp.Pop<u32>()};
- LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(impl->GetSharedFontRegion(font_id).offset);
+Result IPlatformServiceManager::GetSharedMemoryAddressOffset(Out<u32> out_shared_memory_offset,
+ SharedFontType type) {
+ LOG_DEBUG(Service_NS, "called, shared_font_type={}", type);
+ *out_shared_memory_offset = impl->GetSharedFontRegion(static_cast<size_t>(type)).offset;
+ R_SUCCEED();
}
-void IPlatformServiceManager::GetSharedMemoryNativeHandle(HLERequestContext& ctx) {
+Result IPlatformServiceManager::GetSharedMemoryNativeHandle(
+ OutCopyHandle<Kernel::KSharedMemory> out_shared_memory_native_handle) {
// Map backing memory for the font data
LOG_DEBUG(Service_NS, "called");
@@ -256,50 +237,37 @@ void IPlatformServiceManager::GetSharedMemoryNativeHandle(HLERequestContext& ctx
std::memcpy(kernel.GetFontSharedMem().GetPointer(), impl->shared_font->data(),
impl->shared_font->size());
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(&kernel.GetFontSharedMem());
+ // FIXME: this shouldn't belong to the kernel
+ *out_shared_memory_native_handle = &kernel.GetFontSharedMem();
+ R_SUCCEED();
}
-void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext& ctx) {
+Result IPlatformServiceManager::GetSharedFontInOrderOfPriority(
+ OutArray<u32, BufferAttr_HipcMapAlias> out_font_codes,
+ OutArray<u32, BufferAttr_HipcMapAlias> out_font_offsets,
+ OutArray<u32, BufferAttr_HipcMapAlias> out_font_sizes, Out<bool> out_fonts_are_loaded,
+ Out<u32> out_font_count, Set::LanguageCode language_code) {
+ LOG_DEBUG(Service_NS, "called, language_code={:#x}", language_code);
+
// The maximum number of elements that can be returned is 6. Regardless of the available fonts
// or buffer size.
- constexpr std::size_t MaxElementCount = 6;
- IPC::RequestParser rp{ctx};
- const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for
- const std::size_t font_codes_count =
- std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(0));
- const std::size_t font_offsets_count =
- std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(1));
- const std::size_t font_sizes_count =
- std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(2));
- LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code);
-
- IPC::ResponseBuilder rb{ctx, 4};
- std::vector<u32> font_codes;
- std::vector<u32> font_offsets;
- std::vector<u32> font_sizes;
+ constexpr size_t MaxElementCount = 6;
// TODO(ogniK): Have actual priority order
- for (std::size_t i = 0; i < impl->shared_font_regions.size(); i++) {
- font_codes.push_back(static_cast<u32>(i));
- auto region = impl->GetSharedFontRegion(i);
- font_offsets.push_back(region.offset);
- font_sizes.push_back(region.size);
- }
+ const auto max_size = std::min({MaxElementCount, out_font_codes.size(), out_font_offsets.size(),
+ out_font_sizes.size(), impl->shared_font_regions.size()});
- // Resize buffers if game requests smaller size output
- font_codes.resize(std::min(font_codes.size(), font_codes_count));
- font_offsets.resize(std::min(font_offsets.size(), font_offsets_count));
- font_sizes.resize(std::min(font_sizes.size(), font_sizes_count));
+ for (size_t i = 0; i < max_size; i++) {
+ auto region = impl->GetSharedFontRegion(i);
- ctx.WriteBuffer(font_codes, 0);
- ctx.WriteBuffer(font_offsets, 1);
- ctx.WriteBuffer(font_sizes, 2);
+ out_font_codes[i] = static_cast<u32>(i);
+ out_font_offsets[i] = region.offset;
+ out_font_sizes[i] = region.size;
+ }
- rb.Push(ResultSuccess);
- rb.Push<u8>(static_cast<u8>(LoadState::Done)); // Fonts Loaded
- rb.Push<u32>(static_cast<u32>(font_codes.size()));
+ *out_fonts_are_loaded = true;
+ *out_font_count = static_cast<u32>(max_size);
+ R_SUCCEED();
}
} // namespace Service::NS
diff --git a/src/core/hle/service/ns/iplatform_service_manager.h b/src/core/hle/service/ns/platform_service_manager.h
index 03071e02b..b82c385a6 100644
--- a/src/core/hle/service/ns/iplatform_service_manager.h
+++ b/src/core/hle/service/ns/platform_service_manager.h
@@ -5,7 +5,9 @@
#include <memory>
#include <vector>
+#include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"
+#include "core/hle/service/set/settings_types.h"
namespace Service {
@@ -23,6 +25,20 @@ enum class FontArchives : u64 {
ChineseSimple = 0x0100000000000814,
};
+enum class SharedFontType : u32 {
+ JapanUSEuropeStandard = 0,
+ ChineseSimplified = 1,
+ ExtendedChineseSimplified = 2,
+ ChineseTraditional = 3,
+ KoreanHangul = 4,
+ NintendoExtended = 5,
+};
+
+enum class LoadState : u32 {
+ Loading = 0,
+ Loaded = 1,
+};
+
constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{
std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"),
std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"),
@@ -42,12 +58,17 @@ public:
~IPlatformServiceManager() override;
private:
- void RequestLoad(HLERequestContext& ctx);
- void GetLoadState(HLERequestContext& ctx);
- void GetSize(HLERequestContext& ctx);
- void GetSharedMemoryAddressOffset(HLERequestContext& ctx);
- void GetSharedMemoryNativeHandle(HLERequestContext& ctx);
- void GetSharedFontInOrderOfPriority(HLERequestContext& ctx);
+ Result RequestLoad(SharedFontType type);
+ Result GetLoadState(Out<LoadState> out_load_state, SharedFontType type);
+ Result GetSize(Out<u32> out_size, SharedFontType type);
+ Result GetSharedMemoryAddressOffset(Out<u32> out_shared_memory_offset, SharedFontType type);
+ Result GetSharedMemoryNativeHandle(
+ OutCopyHandle<Kernel::KSharedMemory> out_shared_memory_native_handle);
+ Result GetSharedFontInOrderOfPriority(OutArray<u32, BufferAttr_HipcMapAlias> out_font_codes,
+ OutArray<u32, BufferAttr_HipcMapAlias> out_font_offsets,
+ OutArray<u32, BufferAttr_HipcMapAlias> out_font_sizes,
+ Out<bool> out_fonts_are_loaded, Out<u32> out_font_count,
+ Set::LanguageCode language_code);
struct Impl;
std::unique_ptr<Impl> impl;
diff --git a/src/core/hle/service/ns/query_service.cpp b/src/core/hle/service/ns/query_service.cpp
new file mode 100644
index 000000000..946b7fa23
--- /dev/null
+++ b/src/core/hle/service/ns/query_service.cpp
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/logging/log.h"
+#include "common/uuid.h"
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ns/query_service.h"
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+IQueryService::IQueryService(Core::System& system_) : ServiceFramework{system_, "pdm:qry"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "QueryAppletEvent"},
+ {1, nullptr, "QueryPlayStatistics"},
+ {2, nullptr, "QueryPlayStatisticsByUserAccountId"},
+ {3, nullptr, "QueryPlayStatisticsByNetworkServiceAccountId"},
+ {4, nullptr, "QueryPlayStatisticsByApplicationId"},
+ {5, D<&IQueryService::QueryPlayStatisticsByApplicationIdAndUserAccountId>, "QueryPlayStatisticsByApplicationIdAndUserAccountId"},
+ {6, nullptr, "QueryPlayStatisticsByApplicationIdAndNetworkServiceAccountId"},
+ {7, nullptr, "QueryLastPlayTimeV0"},
+ {8, nullptr, "QueryPlayEvent"},
+ {9, nullptr, "GetAvailablePlayEventRange"},
+ {10, nullptr, "QueryAccountEvent"},
+ {11, nullptr, "QueryAccountPlayEvent"},
+ {12, nullptr, "GetAvailableAccountPlayEventRange"},
+ {13, nullptr, "QueryApplicationPlayStatisticsForSystemV0"},
+ {14, nullptr, "QueryRecentlyPlayedApplication"},
+ {15, nullptr, "GetRecentlyPlayedApplicationUpdateEvent"},
+ {16, nullptr, "QueryApplicationPlayStatisticsByUserAccountIdForSystemV0"},
+ {17, nullptr, "QueryLastPlayTime"},
+ {18, nullptr, "QueryApplicationPlayStatisticsForSystem"},
+ {19, nullptr, "QueryApplicationPlayStatisticsByUserAccountIdForSystem"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IQueryService::~IQueryService() = default;
+
+Result IQueryService::QueryPlayStatisticsByApplicationIdAndUserAccountId(
+ Out<PlayStatistics> out_play_statistics, bool unknown, Common::UUID account_id,
+ u64 application_id) {
+ // TODO(German77): Read statistics of the game
+ *out_play_statistics = {
+ .application_id = application_id,
+ .total_launches = 1,
+ };
+
+ LOG_WARNING(Service_NS, "(STUBBED) called. unknown={}. application_id={:016X}, account_id={}",
+ unknown, application_id, account_id.FormattedString());
+ R_SUCCEED();
+}
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/pdm_qry.h b/src/core/hle/service/ns/query_service.h
index c98e01660..6cdbfa277 100644
--- a/src/core/hle/service/ns/pdm_qry.h
+++ b/src/core/hle/service/ns/query_service.h
@@ -3,6 +3,8 @@
#pragma once
+#include "common/uuid.h"
+#include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"
namespace Service::NS {
@@ -20,13 +22,15 @@ struct PlayStatistics {
};
static_assert(sizeof(PlayStatistics) == 0x28, "PlayStatistics is an invalid size");
-class PDM_QRY final : public ServiceFramework<PDM_QRY> {
+class IQueryService final : public ServiceFramework<IQueryService> {
public:
- explicit PDM_QRY(Core::System& system_);
- ~PDM_QRY() override;
+ explicit IQueryService(Core::System& system_);
+ ~IQueryService() override;
private:
- void QueryPlayStatisticsByApplicationIdAndUserAccountId(HLERequestContext& ctx);
+ Result QueryPlayStatisticsByApplicationIdAndUserAccountId(
+ Out<PlayStatistics> out_play_statistics, bool unknown, Common::UUID account_id,
+ u64 application_id);
};
} // namespace Service::NS
diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.cpp b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp
new file mode 100644
index 000000000..9b2ca94a4
--- /dev/null
+++ b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp
@@ -0,0 +1,122 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/settings.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/vfs/vfs.h"
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ns/language.h"
+#include "core/hle/service/ns/ns_results.h"
+#include "core/hle/service/ns/read_only_application_control_data_interface.h"
+#include "core/hle/service/set/settings_server.h"
+
+namespace Service::NS {
+
+IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterface(
+ Core::System& system_)
+ : ServiceFramework{system_, "IReadOnlyApplicationControlDataInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData>, "GetApplicationControlData"},
+ {1, D<&IReadOnlyApplicationControlDataInterface::GetApplicationDesiredLanguage>, "GetApplicationDesiredLanguage"},
+ {2, D<&IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"},
+ {3, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
+ {4, nullptr, "SelectApplicationDesiredLanguage"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IReadOnlyApplicationControlDataInterface::~IReadOnlyApplicationControlDataInterface() = default;
+
+Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData(
+ OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_actual_size,
+ ApplicationControlSource application_control_source, u64 application_id) {
+ LOG_INFO(Service_NS, "called with control_source={}, application_id={:016X}",
+ application_control_source, application_id);
+
+ const FileSys::PatchManager pm{application_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
+ const auto control = pm.GetControlMetadata();
+ const auto size = out_buffer.size();
+
+ const auto icon_size = control.second ? control.second->GetSize() : 0;
+ const auto total_size = sizeof(FileSys::RawNACP) + icon_size;
+
+ if (size < total_size) {
+ LOG_ERROR(Service_NS, "output buffer is too small! (actual={:016X}, expected_min=0x4000)",
+ size);
+ R_THROW(ResultUnknown);
+ }
+
+ if (control.first != nullptr) {
+ const auto bytes = control.first->GetRawBytes();
+ std::memcpy(out_buffer.data(), bytes.data(), bytes.size());
+ } else {
+ LOG_WARNING(Service_NS, "missing NACP data for application_id={:016X}, defaulting to zero",
+ application_id);
+ std::memset(out_buffer.data(), 0, sizeof(FileSys::RawNACP));
+ }
+
+ if (control.second != nullptr) {
+ control.second->Read(out_buffer.data() + sizeof(FileSys::RawNACP), icon_size);
+ } else {
+ LOG_WARNING(Service_NS, "missing icon data for application_id={:016X}", application_id);
+ }
+
+ *out_actual_size = static_cast<u32>(total_size);
+ R_SUCCEED();
+}
+
+Result IReadOnlyApplicationControlDataInterface::GetApplicationDesiredLanguage(
+ Out<ApplicationLanguage> out_desired_language, u32 supported_languages) {
+ LOG_INFO(Service_NS, "called with supported_languages={:08X}", supported_languages);
+
+ // Get language code from settings
+ const auto language_code =
+ Set::GetLanguageCodeFromIndex(static_cast<s32>(Settings::values.language_index.GetValue()));
+
+ // Convert to application language, get priority list
+ const auto application_language = ConvertToApplicationLanguage(language_code);
+ if (application_language == std::nullopt) {
+ LOG_ERROR(Service_NS, "Could not convert application language! language_code={}",
+ language_code);
+ R_THROW(Service::NS::ResultApplicationLanguageNotFound);
+ }
+ const auto priority_list = GetApplicationLanguagePriorityList(*application_language);
+ if (!priority_list) {
+ LOG_ERROR(Service_NS,
+ "Could not find application language priorities! application_language={}",
+ *application_language);
+ R_THROW(Service::NS::ResultApplicationLanguageNotFound);
+ }
+
+ // Try to find a valid language.
+ for (const auto lang : *priority_list) {
+ const auto supported_flag = GetSupportedLanguageFlag(lang);
+ if (supported_languages == 0 || (supported_languages & supported_flag) == supported_flag) {
+ *out_desired_language = lang;
+ R_SUCCEED();
+ }
+ }
+
+ LOG_ERROR(Service_NS, "Could not find a valid language! supported_languages={:08X}",
+ supported_languages);
+ R_THROW(Service::NS::ResultApplicationLanguageNotFound);
+}
+
+Result IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode(
+ Out<u64> out_language_code, ApplicationLanguage application_language) {
+ const auto language_code = ConvertToLanguageCode(application_language);
+ if (language_code == std::nullopt) {
+ LOG_ERROR(Service_NS, "Language not found! application_language={}", application_language);
+ R_THROW(Service::NS::ResultApplicationLanguageNotFound);
+ }
+
+ *out_language_code = static_cast<u64>(*language_code);
+ R_SUCCEED();
+}
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.h b/src/core/hle/service/ns/read_only_application_control_data_interface.h
new file mode 100644
index 000000000..ac099435a
--- /dev/null
+++ b/src/core/hle/service/ns/read_only_application_control_data_interface.h
@@ -0,0 +1,30 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/ns/language.h"
+#include "core/hle/service/ns/ns_types.h"
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+class IReadOnlyApplicationControlDataInterface final
+ : public ServiceFramework<IReadOnlyApplicationControlDataInterface> {
+public:
+ explicit IReadOnlyApplicationControlDataInterface(Core::System& system_);
+ ~IReadOnlyApplicationControlDataInterface() override;
+
+public:
+ Result GetApplicationControlData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
+ Out<u32> out_actual_size,
+ ApplicationControlSource application_control_source,
+ u64 application_id);
+ Result GetApplicationDesiredLanguage(Out<ApplicationLanguage> out_desired_language,
+ u32 supported_languages);
+ Result ConvertApplicationLanguageToLanguageCode(Out<u64> out_language_code,
+ ApplicationLanguage application_language);
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/read_only_application_record_interface.cpp b/src/core/hle/service/ns/read_only_application_record_interface.cpp
new file mode 100644
index 000000000..816a1e1dc
--- /dev/null
+++ b/src/core/hle/service/ns/read_only_application_record_interface.cpp
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ns/read_only_application_record_interface.h"
+
+namespace Service::NS {
+
+IReadOnlyApplicationRecordInterface::IReadOnlyApplicationRecordInterface(Core::System& system_)
+ : ServiceFramework{system_, "IReadOnlyApplicationRecordInterface"} {
+ static const FunctionInfo functions[] = {
+ {0, D<&IReadOnlyApplicationRecordInterface::HasApplicationRecord>, "HasApplicationRecord"},
+ {1, nullptr, "NotifyApplicationFailure"},
+ {2, D<&IReadOnlyApplicationRecordInterface::IsDataCorruptedResult>,
+ "IsDataCorruptedResult"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IReadOnlyApplicationRecordInterface::~IReadOnlyApplicationRecordInterface() = default;
+
+Result IReadOnlyApplicationRecordInterface::HasApplicationRecord(
+ Out<bool> out_has_application_record, u64 program_id) {
+ LOG_WARNING(Service_NS, "(STUBBED) called, program_id={:016X}", program_id);
+ *out_has_application_record = true;
+ R_SUCCEED();
+}
+
+Result IReadOnlyApplicationRecordInterface::IsDataCorruptedResult(
+ Out<bool> out_is_data_corrupted_result, Result result) {
+ LOG_WARNING(Service_NS, "(STUBBED) called, result={:#x}", result.GetInnerValue());
+ *out_is_data_corrupted_result = false;
+ R_SUCCEED();
+}
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/read_only_application_record_interface.h b/src/core/hle/service/ns/read_only_application_record_interface.h
new file mode 100644
index 000000000..d06e8f5e6
--- /dev/null
+++ b/src/core/hle/service/ns/read_only_application_record_interface.h
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+class IReadOnlyApplicationRecordInterface final
+ : public ServiceFramework<IReadOnlyApplicationRecordInterface> {
+public:
+ explicit IReadOnlyApplicationRecordInterface(Core::System& system_);
+ ~IReadOnlyApplicationRecordInterface() override;
+
+private:
+ Result HasApplicationRecord(Out<bool> out_has_application_record, u64 program_id);
+ Result IsDataCorruptedResult(Out<bool> out_is_data_corrupted_result, Result result);
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/service_getter_interface.cpp b/src/core/hle/service/ns/service_getter_interface.cpp
new file mode 100644
index 000000000..1a3dd7166
--- /dev/null
+++ b/src/core/hle/service/ns/service_getter_interface.cpp
@@ -0,0 +1,120 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ns/account_proxy_interface.h"
+#include "core/hle/service/ns/application_manager_interface.h"
+#include "core/hle/service/ns/application_version_interface.h"
+#include "core/hle/service/ns/content_management_interface.h"
+#include "core/hle/service/ns/document_interface.h"
+#include "core/hle/service/ns/download_task_interface.h"
+#include "core/hle/service/ns/dynamic_rights_interface.h"
+#include "core/hle/service/ns/ecommerce_interface.h"
+#include "core/hle/service/ns/factory_reset_interface.h"
+#include "core/hle/service/ns/read_only_application_control_data_interface.h"
+#include "core/hle/service/ns/read_only_application_record_interface.h"
+#include "core/hle/service/ns/service_getter_interface.h"
+
+namespace Service::NS {
+
+IServiceGetterInterface::IServiceGetterInterface(Core::System& system_, const char* name)
+ : ServiceFramework{system_, name} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {7988, D<&IServiceGetterInterface::GetDynamicRightsInterface>, "GetDynamicRightsInterface"},
+ {7989, D<&IServiceGetterInterface::GetReadOnlyApplicationControlDataInterface>, "GetReadOnlyApplicationControlDataInterface"},
+ {7991, D<&IServiceGetterInterface::GetReadOnlyApplicationRecordInterface>, "GetReadOnlyApplicationRecordInterface"},
+ {7992, D<&IServiceGetterInterface::GetECommerceInterface>, "GetECommerceInterface"},
+ {7993, D<&IServiceGetterInterface::GetApplicationVersionInterface>, "GetApplicationVersionInterface"},
+ {7994, D<&IServiceGetterInterface::GetFactoryResetInterface>, "GetFactoryResetInterface"},
+ {7995, D<&IServiceGetterInterface::GetAccountProxyInterface>, "GetAccountProxyInterface"},
+ {7996, D<&IServiceGetterInterface::GetApplicationManagerInterface>, "GetApplicationManagerInterface"},
+ {7997, D<&IServiceGetterInterface::GetDownloadTaskInterface>, "GetDownloadTaskInterface"},
+ {7998, D<&IServiceGetterInterface::GetContentManagementInterface>, "GetContentManagementInterface"},
+ {7999, D<&IServiceGetterInterface::GetDocumentInterface>, "GetDocumentInterface"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IServiceGetterInterface::~IServiceGetterInterface() = default;
+
+Result IServiceGetterInterface::GetDynamicRightsInterface(
+ Out<SharedPointer<IDynamicRightsInterface>> out_interface) {
+ LOG_DEBUG(Service_NS, "called");
+ *out_interface = std::make_shared<IDynamicRightsInterface>(system);
+ R_SUCCEED();
+}
+
+Result IServiceGetterInterface::GetReadOnlyApplicationControlDataInterface(
+ Out<SharedPointer<IReadOnlyApplicationControlDataInterface>> out_interface) {
+ LOG_DEBUG(Service_NS, "called");
+ *out_interface = std::make_shared<IReadOnlyApplicationControlDataInterface>(system);
+ R_SUCCEED();
+}
+
+Result IServiceGetterInterface::GetReadOnlyApplicationRecordInterface(
+ Out<SharedPointer<IReadOnlyApplicationRecordInterface>> out_interface) {
+ LOG_DEBUG(Service_NS, "called");
+ *out_interface = std::make_shared<IReadOnlyApplicationRecordInterface>(system);
+ R_SUCCEED();
+}
+
+Result IServiceGetterInterface::GetECommerceInterface(
+ Out<SharedPointer<IECommerceInterface>> out_interface) {
+ LOG_DEBUG(Service_NS, "called");
+ *out_interface = std::make_shared<IECommerceInterface>(system);
+ R_SUCCEED();
+}
+
+Result IServiceGetterInterface::GetApplicationVersionInterface(
+ Out<SharedPointer<IApplicationVersionInterface>> out_interface) {
+ LOG_DEBUG(Service_NS, "called");
+ *out_interface = std::make_shared<IApplicationVersionInterface>(system);
+ R_SUCCEED();
+}
+
+Result IServiceGetterInterface::GetFactoryResetInterface(
+ Out<SharedPointer<IFactoryResetInterface>> out_interface) {
+ LOG_DEBUG(Service_NS, "called");
+ *out_interface = std::make_shared<IFactoryResetInterface>(system);
+ R_SUCCEED();
+}
+
+Result IServiceGetterInterface::GetAccountProxyInterface(
+ Out<SharedPointer<IAccountProxyInterface>> out_interface) {
+ LOG_DEBUG(Service_NS, "called");
+ *out_interface = std::make_shared<IAccountProxyInterface>(system);
+ R_SUCCEED();
+}
+
+Result IServiceGetterInterface::GetApplicationManagerInterface(
+ Out<SharedPointer<IApplicationManagerInterface>> out_interface) {
+ LOG_DEBUG(Service_NS, "called");
+ *out_interface = std::make_shared<IApplicationManagerInterface>(system);
+ R_SUCCEED();
+}
+
+Result IServiceGetterInterface::GetDownloadTaskInterface(
+ Out<SharedPointer<IDownloadTaskInterface>> out_interface) {
+ LOG_DEBUG(Service_NS, "called");
+ *out_interface = std::make_shared<IDownloadTaskInterface>(system);
+ R_SUCCEED();
+}
+
+Result IServiceGetterInterface::GetContentManagementInterface(
+ Out<SharedPointer<IContentManagementInterface>> out_interface) {
+ LOG_DEBUG(Service_NS, "called");
+ *out_interface = std::make_shared<IContentManagementInterface>(system);
+ R_SUCCEED();
+}
+
+Result IServiceGetterInterface::GetDocumentInterface(
+ Out<SharedPointer<IDocumentInterface>> out_interface) {
+ LOG_DEBUG(Service_NS, "called");
+ *out_interface = std::make_shared<IDocumentInterface>(system);
+ R_SUCCEED();
+}
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/service_getter_interface.h b/src/core/hle/service/ns/service_getter_interface.h
new file mode 100644
index 000000000..bbc18d444
--- /dev/null
+++ b/src/core/hle/service/ns/service_getter_interface.h
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+class IDynamicRightsInterface;
+class IReadOnlyApplicationControlDataInterface;
+class IReadOnlyApplicationRecordInterface;
+class IECommerceInterface;
+class IApplicationVersionInterface;
+class IFactoryResetInterface;
+class IAccountProxyInterface;
+class IApplicationManagerInterface;
+class IDownloadTaskInterface;
+class IContentManagementInterface;
+class IDocumentInterface;
+
+class IServiceGetterInterface : public ServiceFramework<IServiceGetterInterface> {
+public:
+ explicit IServiceGetterInterface(Core::System& system_, const char* name);
+ ~IServiceGetterInterface() override;
+
+public:
+ Result GetDynamicRightsInterface(Out<SharedPointer<IDynamicRightsInterface>> out_interface);
+ Result GetReadOnlyApplicationControlDataInterface(
+ Out<SharedPointer<IReadOnlyApplicationControlDataInterface>> out_interface);
+ Result GetReadOnlyApplicationRecordInterface(
+ Out<SharedPointer<IReadOnlyApplicationRecordInterface>> out_interface);
+ Result GetECommerceInterface(Out<SharedPointer<IECommerceInterface>> out_interface);
+ Result GetApplicationVersionInterface(
+ Out<SharedPointer<IApplicationVersionInterface>> out_interface);
+ Result GetFactoryResetInterface(Out<SharedPointer<IFactoryResetInterface>> out_interface);
+ Result GetAccountProxyInterface(Out<SharedPointer<IAccountProxyInterface>> out_interface);
+ Result GetApplicationManagerInterface(
+ Out<SharedPointer<IApplicationManagerInterface>> out_interface);
+ Result GetDownloadTaskInterface(Out<SharedPointer<IDownloadTaskInterface>> out_interface);
+ Result GetContentManagementInterface(
+ Out<SharedPointer<IContentManagementInterface>> out_interface);
+ Result GetDocumentInterface(Out<SharedPointer<IDocumentInterface>> out_interface);
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/system_update_control.cpp b/src/core/hle/service/ns/system_update_control.cpp
new file mode 100644
index 000000000..f5f5cfd90
--- /dev/null
+++ b/src/core/hle/service/ns/system_update_control.cpp
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ns/system_update_control.h"
+
+namespace Service::NS {
+
+ISystemUpdateControl::ISystemUpdateControl(Core::System& system_)
+ : ServiceFramework{system_, "ISystemUpdateControl"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "HasDownloaded"},
+ {1, nullptr, "RequestCheckLatestUpdate"},
+ {2, nullptr, "RequestDownloadLatestUpdate"},
+ {3, nullptr, "GetDownloadProgress"},
+ {4, nullptr, "ApplyDownloadedUpdate"},
+ {5, nullptr, "RequestPrepareCardUpdate"},
+ {6, nullptr, "GetPrepareCardUpdateProgress"},
+ {7, nullptr, "HasPreparedCardUpdate"},
+ {8, nullptr, "ApplyCardUpdate"},
+ {9, nullptr, "GetDownloadedEulaDataSize"},
+ {10, nullptr, "GetDownloadedEulaData"},
+ {11, nullptr, "SetupCardUpdate"},
+ {12, nullptr, "GetPreparedCardUpdateEulaDataSize"},
+ {13, nullptr, "GetPreparedCardUpdateEulaData"},
+ {14, nullptr, "SetupCardUpdateViaSystemUpdater"},
+ {15, nullptr, "HasReceived"},
+ {16, nullptr, "RequestReceiveSystemUpdate"},
+ {17, nullptr, "GetReceiveProgress"},
+ {18, nullptr, "ApplyReceivedUpdate"},
+ {19, nullptr, "GetReceivedEulaDataSize"},
+ {20, nullptr, "GetReceivedEulaData"},
+ {21, nullptr, "SetupToReceiveSystemUpdate"},
+ {22, nullptr, "RequestCheckLatestUpdateIncludesRebootlessUpdate"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+ISystemUpdateControl::~ISystemUpdateControl() = default;
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/system_update_control.h b/src/core/hle/service/ns/system_update_control.h
new file mode 100644
index 000000000..a30a09000
--- /dev/null
+++ b/src/core/hle/service/ns/system_update_control.h
@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+class ISystemUpdateControl final : public ServiceFramework<ISystemUpdateControl> {
+public:
+ explicit ISystemUpdateControl(Core::System& system_);
+ ~ISystemUpdateControl() override;
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/system_update_interface.cpp b/src/core/hle/service/ns/system_update_interface.cpp
new file mode 100644
index 000000000..7e22ca3db
--- /dev/null
+++ b/src/core/hle/service/ns/system_update_interface.cpp
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ns/system_update_control.h"
+#include "core/hle/service/ns/system_update_interface.h"
+
+namespace Service::NS {
+
+ISystemUpdateInterface::ISystemUpdateInterface(Core::System& system_)
+ : ServiceFramework{system_, "ns:su"}, service_context{system_, "ns:su"},
+ update_notification_event{service_context} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, D<&ISystemUpdateInterface::GetBackgroundNetworkUpdateState>, "GetBackgroundNetworkUpdateState"},
+ {1, D<&ISystemUpdateInterface::OpenSystemUpdateControl>, "OpenSystemUpdateControl"},
+ {2, nullptr, "NotifyExFatDriverRequired"},
+ {3, nullptr, "ClearExFatDriverStatusForDebug"},
+ {4, nullptr, "RequestBackgroundNetworkUpdate"},
+ {5, nullptr, "NotifyBackgroundNetworkUpdate"},
+ {6, nullptr, "NotifyExFatDriverDownloadedForDebug"},
+ {9, D<&ISystemUpdateInterface::GetSystemUpdateNotificationEventForContentDelivery>, "GetSystemUpdateNotificationEventForContentDelivery"},
+ {10, nullptr, "NotifySystemUpdateForContentDelivery"},
+ {11, nullptr, "PrepareShutdown"},
+ {12, nullptr, "Unknown12"},
+ {13, nullptr, "Unknown13"},
+ {14, nullptr, "Unknown14"},
+ {15, nullptr, "Unknown15"},
+ {16, nullptr, "DestroySystemUpdateTask"},
+ {17, nullptr, "RequestSendSystemUpdate"},
+ {18, nullptr, "GetSendSystemUpdateProgress"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+ISystemUpdateInterface::~ISystemUpdateInterface() = default;
+
+Result ISystemUpdateInterface::GetBackgroundNetworkUpdateState(
+ Out<BackgroundNetworkUpdateState> out_background_network_update_state) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+ *out_background_network_update_state = BackgroundNetworkUpdateState::None;
+ R_SUCCEED();
+}
+
+Result ISystemUpdateInterface::OpenSystemUpdateControl(
+ Out<SharedPointer<ISystemUpdateControl>> out_system_update_control) {
+ LOG_WARNING(Service_NS, "(STUBBED) called");
+ *out_system_update_control = std::make_shared<ISystemUpdateControl>(system);
+ R_SUCCEED();
+}
+
+Result ISystemUpdateInterface::GetSystemUpdateNotificationEventForContentDelivery(
+ OutCopyHandle<Kernel::KReadableEvent> out_event) {
+ LOG_WARNING(Service_NS, "(STUBBED) called");
+ *out_event = update_notification_event.GetHandle();
+ R_SUCCEED();
+}
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/system_update_interface.h b/src/core/hle/service/ns/system_update_interface.h
new file mode 100644
index 000000000..36a2880ec
--- /dev/null
+++ b/src/core/hle/service/ns/system_update_interface.h
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/ns/ns_types.h"
+#include "core/hle/service/os/event.h"
+#include "core/hle/service/service.h"
+
+namespace Kernel {
+class KReadableEvent;
+}
+
+namespace Service::NS {
+
+class ISystemUpdateControl;
+
+class ISystemUpdateInterface final : public ServiceFramework<ISystemUpdateInterface> {
+public:
+ explicit ISystemUpdateInterface(Core::System& system_);
+ ~ISystemUpdateInterface() override;
+
+private:
+ Result GetBackgroundNetworkUpdateState(
+ Out<BackgroundNetworkUpdateState> out_background_network_update_state);
+ Result OpenSystemUpdateControl(
+ Out<SharedPointer<ISystemUpdateControl>> out_system_update_control);
+ Result GetSystemUpdateNotificationEventForContentDelivery(
+ OutCopyHandle<Kernel::KReadableEvent> out_event);
+
+private:
+ KernelHelpers::ServiceContext service_context;
+ Event update_notification_event;
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/vulnerability_manager_interface.cpp b/src/core/hle/service/ns/vulnerability_manager_interface.cpp
new file mode 100644
index 000000000..69c21fb89
--- /dev/null
+++ b/src/core/hle/service/ns/vulnerability_manager_interface.cpp
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ns/vulnerability_manager_interface.h"
+
+namespace Service::NS {
+
+IVulnerabilityManagerInterface::IVulnerabilityManagerInterface(Core::System& system_)
+ : ServiceFramework{system_, "ns:vm"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {1200, D<&IVulnerabilityManagerInterface::NeedsUpdateVulnerability>, "NeedsUpdateVulnerability"},
+ {1201, nullptr, "UpdateSafeSystemVersionForDebug"},
+ {1202, nullptr, "GetSafeSystemVersion"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IVulnerabilityManagerInterface::~IVulnerabilityManagerInterface() = default;
+
+Result IVulnerabilityManagerInterface::NeedsUpdateVulnerability(
+ Out<bool> out_needs_update_vulnerability) {
+ LOG_WARNING(Service_NS, "(STUBBED) called");
+ *out_needs_update_vulnerability = false;
+ R_SUCCEED();
+}
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/vulnerability_manager_interface.h b/src/core/hle/service/ns/vulnerability_manager_interface.h
new file mode 100644
index 000000000..c689cf7ec
--- /dev/null
+++ b/src/core/hle/service/ns/vulnerability_manager_interface.h
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Service::NS {
+
+class IVulnerabilityManagerInterface final
+ : public ServiceFramework<IVulnerabilityManagerInterface> {
+public:
+ explicit IVulnerabilityManagerInterface(Core::System& system_);
+ ~IVulnerabilityManagerInterface() override;
+
+private:
+ Result NeedsUpdateVulnerability(Out<bool> out_needs_update_vulnerability);
+};
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
index 250d01de3..0265d55f2 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
@@ -92,11 +92,11 @@ NvResult nvhost_ctrl::IocCtrlEventWait(IocCtrlEventWaitParams& params, bool is_a
bool must_unmark_fail = !is_allocation;
const u32 event_id = params.value.raw;
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (must_unmark_fail) {
events[event_id].fails = 0;
}
- });
+ };
const u32 fence_id = static_cast<u32>(params.fence.id);
diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp
index cb256e5b4..03eb507b9 100644
--- a/src/core/hle/service/nvdrv/nvdrv.cpp
+++ b/src/core/hle/service/nvdrv/nvdrv.cpp
@@ -42,7 +42,7 @@ void EventInterface::FreeEvent(Kernel::KEvent* event) {
module.service_context.CloseEvent(event);
}
-void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) {
+void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
auto module = std::make_shared<Module>(system);
const auto NvdrvInterfaceFactoryForApplication = [&, module] {
@@ -62,7 +62,6 @@ void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) {
server_manager->RegisterNamedService("nvdrv:s", NvdrvInterfaceFactoryForSysmodules);
server_manager->RegisterNamedService("nvdrv:t", NvdrvInterfaceFactoryForTesting);
server_manager->RegisterNamedService("nvmemp", std::make_shared<NVMEMP>(system));
- nvnflinger.SetNVDrvInstance(module);
ServerManager::RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h
index c594f0e5e..b76f81e59 100644
--- a/src/core/hle/service/nvdrv/nvdrv.h
+++ b/src/core/hle/service/nvdrv/nvdrv.h
@@ -10,13 +10,11 @@
#include <span>
#include <string>
#include <unordered_map>
-#include <vector>
#include "common/common_types.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/nvdata.h"
-#include "core/hle/service/nvnflinger/ui/fence.h"
#include "core/hle/service/service.h"
namespace Core {
@@ -27,10 +25,6 @@ namespace Kernel {
class KEvent;
}
-namespace Service::Nvnflinger {
-class Nvnflinger;
-}
-
namespace Service::Nvidia {
namespace NvCore {
@@ -99,7 +93,6 @@ public:
private:
friend class EventInterface;
- friend class Service::Nvnflinger::Nvnflinger;
/// Manages syncpoints on the host
NvCore::Container container;
@@ -118,6 +111,6 @@ private:
std::unordered_map<std::string, std::function<FilesContainerType::iterator(DeviceFD)>> builders;
};
-void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system);
+void LoopProcess(Core::System& system);
} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdrv_interface.cpp b/src/core/hle/service/nvdrv/nvdrv_interface.cpp
index ffe72f281..258970fd5 100644
--- a/src/core/hle/service/nvdrv/nvdrv_interface.cpp
+++ b/src/core/hle/service/nvdrv/nvdrv_interface.cpp
@@ -154,10 +154,10 @@ void NVDRV::Close(HLERequestContext& ctx) {
void NVDRV::Initialize(HLERequestContext& ctx) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
- SCOPE_EXIT({
+ SCOPE_EXIT {
rb.Push(ResultSuccess);
rb.PushEnum(NvResult::Success);
- });
+ };
if (is_initialized) {
// No need to initialize again
@@ -263,8 +263,10 @@ NVDRV::NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char*
}
NVDRV::~NVDRV() {
- auto& container = nvdrv->GetContainer();
- container.CloseSession(session_id);
+ if (is_initialized) {
+ auto& container = nvdrv->GetContainer();
+ container.CloseSession(session_id);
+ }
}
} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdrv_interface.h b/src/core/hle/service/nvdrv/nvdrv_interface.h
index f2195ae1e..c72f92597 100644
--- a/src/core/hle/service/nvdrv/nvdrv_interface.h
+++ b/src/core/hle/service/nvdrv/nvdrv_interface.h
@@ -16,6 +16,10 @@ public:
explicit NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name);
~NVDRV() override;
+ std::shared_ptr<Module> GetModule() const {
+ return nvdrv;
+ }
+
private:
void Open(HLERequestContext& ctx);
void Ioctl1(HLERequestContext& ctx);
diff --git a/src/core/hle/service/nvnflinger/binder.h b/src/core/hle/service/nvnflinger/binder.h
index 179938192..124accb94 100644
--- a/src/core/hle/service/nvnflinger/binder.h
+++ b/src/core/hle/service/nvnflinger/binder.h
@@ -20,29 +20,12 @@ class HLERequestContext;
namespace Service::android {
-enum class TransactionId {
- RequestBuffer = 1,
- SetBufferCount = 2,
- DequeueBuffer = 3,
- DetachBuffer = 4,
- DetachNextBuffer = 5,
- AttachBuffer = 6,
- QueueBuffer = 7,
- CancelBuffer = 8,
- Query = 9,
- Connect = 10,
- Disconnect = 11,
- AllocateBuffers = 13,
- SetPreallocatedBuffer = 14,
- GetBufferHistory = 17,
-};
-
class IBinder {
public:
virtual ~IBinder() = default;
- virtual void Transact(android::TransactionId code, u32 flags, std::span<const u8> parcel_data,
- std::span<u8> parcel_reply) = 0;
- virtual Kernel::KReadableEvent& GetNativeHandle() = 0;
+ virtual void Transact(u32 code, std::span<const u8> parcel_data, std::span<u8> parcel_reply,
+ u32 flags) = 0;
+ virtual Kernel::KReadableEvent* GetNativeHandle(u32 type_id) = 0;
};
} // namespace Service::android
diff --git a/src/core/hle/service/nvnflinger/buffer_item_consumer.cpp b/src/core/hle/service/nvnflinger/buffer_item_consumer.cpp
index cf151ea3a..123507123 100644
--- a/src/core/hle/service/nvnflinger/buffer_item_consumer.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_item_consumer.cpp
@@ -12,7 +12,7 @@
namespace Service::android {
-BufferItemConsumer::BufferItemConsumer(std::unique_ptr<BufferQueueConsumer> consumer_)
+BufferItemConsumer::BufferItemConsumer(std::shared_ptr<BufferQueueConsumer> consumer_)
: ConsumerBase{std::move(consumer_)} {}
Status BufferItemConsumer::AcquireBuffer(BufferItem* item, std::chrono::nanoseconds present_when,
diff --git a/src/core/hle/service/nvnflinger/buffer_item_consumer.h b/src/core/hle/service/nvnflinger/buffer_item_consumer.h
index e0c6b3604..9f95c9280 100644
--- a/src/core/hle/service/nvnflinger/buffer_item_consumer.h
+++ b/src/core/hle/service/nvnflinger/buffer_item_consumer.h
@@ -19,7 +19,7 @@ class BufferItem;
class BufferItemConsumer final : public ConsumerBase {
public:
- explicit BufferItemConsumer(std::unique_ptr<BufferQueueConsumer> consumer);
+ explicit BufferItemConsumer(std::shared_ptr<BufferQueueConsumer> consumer);
Status AcquireBuffer(BufferItem* item, std::chrono::nanoseconds present_when,
bool wait_for_fence = true);
Status ReleaseBuffer(const BufferItem& item, const Fence& release_fence);
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp
index bbe8e06d4..3bc23aa97 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp
@@ -4,12 +4,13 @@
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueConsumer.cpp
+#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/service/nvnflinger/buffer_item.h"
#include "core/hle/service/nvnflinger/buffer_queue_consumer.h"
#include "core/hle/service/nvnflinger/buffer_queue_core.h"
+#include "core/hle/service/nvnflinger/parcel.h"
#include "core/hle/service/nvnflinger/producer_listener.h"
-#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
namespace Service::android {
@@ -254,4 +255,77 @@ Status BufferQueueConsumer::GetReleasedBuffers(u64* out_slot_mask) {
return Status::NoError;
}
+void BufferQueueConsumer::Transact(u32 code, std::span<const u8> parcel_data,
+ std::span<u8> parcel_reply, u32 flags) {
+ // Values used by BnGraphicBufferConsumer onTransact
+ enum class TransactionId {
+ AcquireBuffer = 1,
+ DetachBuffer = 2,
+ AttachBuffer = 3,
+ ReleaseBuffer = 4,
+ ConsumerConnect = 5,
+ ConsumerDisconnect = 6,
+ GetReleasedBuffers = 7,
+ SetDefaultBufferSize = 8,
+ SetDefaultMaxBufferCount = 9,
+ DisableAsyncBuffer = 10,
+ SetMaxAcquiredBufferCount = 11,
+ SetConsumerName = 12,
+ SetDefaultBufferFormat = 13,
+ SetConsumerUsageBits = 14,
+ SetTransformHint = 15,
+ GetSidebandStream = 16,
+ Unknown18 = 18,
+ Unknown20 = 20,
+ };
+
+ Status status{Status::NoError};
+ InputParcel parcel_in{parcel_data};
+ OutputParcel parcel_out{};
+
+ switch (static_cast<TransactionId>(code)) {
+ case TransactionId::AcquireBuffer: {
+ BufferItem item;
+ const s64 present_when = parcel_in.Read<s64>();
+
+ status = AcquireBuffer(&item, std::chrono::nanoseconds{present_when});
+
+ // TODO: can't write this directly, needs a flattener for the sp<GraphicBuffer>
+ // parcel_out.WriteFlattened(item);
+ UNREACHABLE();
+ }
+ case TransactionId::ReleaseBuffer: {
+ const s32 slot = parcel_in.Read<s32>();
+ const u64 frame_number = parcel_in.Read<u64>();
+ const auto release_fence = parcel_in.ReadFlattened<Fence>();
+
+ status = ReleaseBuffer(slot, frame_number, release_fence);
+
+ break;
+ }
+ case TransactionId::GetReleasedBuffers: {
+ u64 slot_mask = 0;
+
+ status = GetReleasedBuffers(&slot_mask);
+
+ parcel_out.Write(slot_mask);
+ break;
+ }
+ default:
+ ASSERT_MSG(false, "called, code={} flags={}", code, flags);
+ break;
+ }
+
+ parcel_out.Write(status);
+
+ const auto serialized = parcel_out.Serialize();
+ std::memcpy(parcel_reply.data(), serialized.data(),
+ std::min(parcel_reply.size(), serialized.size()));
+}
+
+Kernel::KReadableEvent* BufferQueueConsumer::GetNativeHandle(u32 type_id) {
+ ASSERT_MSG(false, "called, type_id={}", type_id);
+ return nullptr;
+}
+
} // namespace Service::android
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_consumer.h b/src/core/hle/service/nvnflinger/buffer_queue_consumer.h
index 0a61e8dbd..a9226f1c3 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_consumer.h
+++ b/src/core/hle/service/nvnflinger/buffer_queue_consumer.h
@@ -10,6 +10,7 @@
#include <memory>
#include "common/common_types.h"
+#include "core/hle/service/nvnflinger/binder.h"
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
#include "core/hle/service/nvnflinger/status.h"
@@ -19,10 +20,10 @@ class BufferItem;
class BufferQueueCore;
class IConsumerListener;
-class BufferQueueConsumer final {
+class BufferQueueConsumer final : public IBinder {
public:
explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_);
- ~BufferQueueConsumer();
+ ~BufferQueueConsumer() override;
Status AcquireBuffer(BufferItem* out_buffer, std::chrono::nanoseconds expected_present);
Status ReleaseBuffer(s32 slot, u64 frame_number, const Fence& release_fence);
@@ -30,6 +31,11 @@ public:
Status Disconnect();
Status GetReleasedBuffers(u64* out_slot_mask);
+ void Transact(u32 code, std::span<const u8> parcel_data, std::span<u8> parcel_reply,
+ u32 flags) override;
+
+ Kernel::KReadableEvent* GetNativeHandle(u32 type_id) override;
+
private:
std::shared_ptr<BufferQueueCore> core;
BufferQueueDefs::SlotsType& slots;
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
index ec83beb9b..9e5091eeb 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
@@ -6,12 +6,9 @@
#include "common/assert.h"
#include "common/logging/log.h"
-#include "common/settings.h"
-#include "core/core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nvnflinger/buffer_queue_core.h"
#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
@@ -19,7 +16,6 @@
#include "core/hle/service/nvnflinger/parcel.h"
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
#include "core/hle/service/nvnflinger/window.h"
-#include "core/hle/service/vi/vi.h"
namespace Service::android {
@@ -807,13 +803,31 @@ Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot,
return Status::NoError;
}
-void BufferQueueProducer::Transact(TransactionId code, u32 flags, std::span<const u8> parcel_data,
- std::span<u8> parcel_reply) {
+void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
+ std::span<u8> parcel_reply, u32 flags) {
+ // Values used by BnGraphicBufferProducer onTransact
+ enum class TransactionId {
+ RequestBuffer = 1,
+ SetBufferCount = 2,
+ DequeueBuffer = 3,
+ DetachBuffer = 4,
+ DetachNextBuffer = 5,
+ AttachBuffer = 6,
+ QueueBuffer = 7,
+ CancelBuffer = 8,
+ Query = 9,
+ Connect = 10,
+ Disconnect = 11,
+ AllocateBuffers = 13,
+ SetPreallocatedBuffer = 14,
+ GetBufferHistory = 17,
+ };
+
Status status{Status::NoError};
InputParcel parcel_in{parcel_data};
OutputParcel parcel_out{};
- switch (code) {
+ switch (static_cast<TransactionId>(code)) {
case TransactionId::Connect: {
const auto enable_listener = parcel_in.Read<bool>();
const auto api = parcel_in.Read<NativeWindowApi>();
@@ -923,8 +937,8 @@ void BufferQueueProducer::Transact(TransactionId code, u32 flags, std::span<cons
std::min(parcel_reply.size(), serialized.size()));
}
-Kernel::KReadableEvent& BufferQueueProducer::GetNativeHandle() {
- return buffer_wait_event->GetReadableEvent();
+Kernel::KReadableEvent* BufferQueueProducer::GetNativeHandle(u32 type_id) {
+ return &buffer_wait_event->GetReadableEvent();
}
} // namespace Service::android
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.h b/src/core/hle/service/nvnflinger/buffer_queue_producer.h
index 4682b0f84..048523514 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_producer.h
+++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.h
@@ -45,12 +45,12 @@ public:
explicit BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
std::shared_ptr<BufferQueueCore> buffer_queue_core_,
Service::Nvidia::NvCore::NvMap& nvmap_);
- ~BufferQueueProducer();
+ ~BufferQueueProducer() override;
- void Transact(android::TransactionId code, u32 flags, std::span<const u8> parcel_data,
- std::span<u8> parcel_reply) override;
+ void Transact(u32 code, std::span<const u8> parcel_data, std::span<u8> parcel_reply,
+ u32 flags) override;
- Kernel::KReadableEvent& GetNativeHandle() override;
+ Kernel::KReadableEvent* GetNativeHandle(u32 type_id) override;
public:
Status RequestBuffer(s32 slot, std::shared_ptr<GraphicBuffer>* buf);
diff --git a/src/core/hle/service/nvnflinger/consumer_base.cpp b/src/core/hle/service/nvnflinger/consumer_base.cpp
index 1059e72bf..e360ebfd8 100644
--- a/src/core/hle/service/nvnflinger/consumer_base.cpp
+++ b/src/core/hle/service/nvnflinger/consumer_base.cpp
@@ -14,7 +14,7 @@
namespace Service::android {
-ConsumerBase::ConsumerBase(std::unique_ptr<BufferQueueConsumer> consumer_)
+ConsumerBase::ConsumerBase(std::shared_ptr<BufferQueueConsumer> consumer_)
: consumer{std::move(consumer_)} {}
ConsumerBase::~ConsumerBase() {
diff --git a/src/core/hle/service/nvnflinger/consumer_base.h b/src/core/hle/service/nvnflinger/consumer_base.h
index ea3e9e97a..b29c16f86 100644
--- a/src/core/hle/service/nvnflinger/consumer_base.h
+++ b/src/core/hle/service/nvnflinger/consumer_base.h
@@ -27,7 +27,7 @@ public:
void Abandon();
protected:
- explicit ConsumerBase(std::unique_ptr<BufferQueueConsumer> consumer_);
+ explicit ConsumerBase(std::shared_ptr<BufferQueueConsumer> consumer_);
~ConsumerBase() override;
void OnFrameAvailable(const BufferItem& item) override;
@@ -54,7 +54,7 @@ protected:
bool is_abandoned{};
- std::unique_ptr<BufferQueueConsumer> consumer;
+ std::shared_ptr<BufferQueueConsumer> consumer;
mutable std::mutex mutex;
};
diff --git a/src/core/hle/service/nvnflinger/display.h b/src/core/hle/service/nvnflinger/display.h
new file mode 100644
index 000000000..f27cbf144
--- /dev/null
+++ b/src/core/hle/service/nvnflinger/display.h
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <list>
+
+#include "core/hle/service/nvnflinger/buffer_item_consumer.h"
+#include "core/hle/service/nvnflinger/hwc_layer.h"
+
+namespace Service::Nvnflinger {
+
+struct Layer {
+ explicit Layer(std::shared_ptr<android::BufferItemConsumer> buffer_item_consumer_,
+ s32 consumer_id_)
+ : buffer_item_consumer(std::move(buffer_item_consumer_)), consumer_id(consumer_id_),
+ blending(LayerBlending::None), visible(true) {}
+ ~Layer() {
+ buffer_item_consumer->Abandon();
+ }
+
+ std::shared_ptr<android::BufferItemConsumer> buffer_item_consumer;
+ s32 consumer_id;
+ LayerBlending blending;
+ bool visible;
+};
+
+struct LayerStack {
+ std::list<Layer> layers;
+};
+
+struct Display {
+ explicit Display(u64 id_) {
+ id = id_;
+ }
+
+ Layer* FindLayer(s32 consumer_id) {
+ for (auto& layer : stack.layers) {
+ if (layer.consumer_id == consumer_id) {
+ return &layer;
+ }
+ }
+
+ return nullptr;
+ }
+
+ bool HasLayers() {
+ return !stack.layers.empty();
+ }
+
+ u64 id;
+ LayerStack stack;
+};
+
+} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/hardware_composer.cpp b/src/core/hle/service/nvnflinger/hardware_composer.cpp
index be7eb97a3..02215a786 100644
--- a/src/core/hle/service/nvnflinger/hardware_composer.cpp
+++ b/src/core/hle/service/nvnflinger/hardware_composer.cpp
@@ -10,8 +10,6 @@
#include "core/hle/service/nvnflinger/hardware_composer.h"
#include "core/hle/service/nvnflinger/hwc_layer.h"
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
-#include "core/hle/service/vi/display/vi_display.h"
-#include "core/hle/service/vi/layer/vi_layer.h"
namespace Service::Nvnflinger {
@@ -44,7 +42,7 @@ s32 NormalizeSwapInterval(f32* out_speed_scale, s32 swap_interval) {
HardwareComposer::HardwareComposer() = default;
HardwareComposer::~HardwareComposer() = default;
-u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, VI::Display& display,
+u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
Nvidia::Devices::nvdisp_disp0& nvdisp) {
boost::container::small_vector<HwcLayer, 2> composition_stack;
@@ -56,12 +54,11 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, VI::Display& display,
bool has_acquired_buffer{};
// Acquire all necessary framebuffers.
- for (size_t i = 0; i < display.GetNumLayers(); i++) {
- auto& layer = display.GetLayer(i);
- auto layer_id = layer.GetLayerId();
+ for (auto& layer : display.stack.layers) {
+ auto consumer_id = layer.consumer_id;
// Try to fetch the framebuffer (either new or stale).
- const auto result = this->CacheFramebufferLocked(layer, layer_id);
+ const auto result = this->CacheFramebufferLocked(layer, consumer_id);
// If we failed, skip this layer.
if (result == CacheStatus::NoBufferAvailable) {
@@ -73,24 +70,26 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, VI::Display& display,
has_acquired_buffer = true;
}
- const auto& buffer = m_framebuffers[layer_id];
+ const auto& buffer = m_framebuffers[consumer_id];
const auto& item = buffer.item;
const auto& igbp_buffer = *item.graphic_buffer;
// TODO: get proper Z-index from layer
- composition_stack.emplace_back(HwcLayer{
- .buffer_handle = igbp_buffer.BufferId(),
- .offset = igbp_buffer.Offset(),
- .format = igbp_buffer.ExternalFormat(),
- .width = igbp_buffer.Width(),
- .height = igbp_buffer.Height(),
- .stride = igbp_buffer.Stride(),
- .z_index = 0,
- .blending = layer.GetBlending(),
- .transform = static_cast<android::BufferTransformFlags>(item.transform),
- .crop_rect = item.crop,
- .acquire_fence = item.fence,
- });
+ if (layer.visible) {
+ composition_stack.emplace_back(HwcLayer{
+ .buffer_handle = igbp_buffer.BufferId(),
+ .offset = igbp_buffer.Offset(),
+ .format = igbp_buffer.ExternalFormat(),
+ .width = igbp_buffer.Width(),
+ .height = igbp_buffer.Height(),
+ .stride = igbp_buffer.Stride(),
+ .z_index = 0,
+ .blending = layer.blending,
+ .transform = static_cast<android::BufferTransformFlags>(item.transform),
+ .crop_rect = item.crop,
+ .acquire_fence = item.fence,
+ });
+ }
// We need to compose again either before this frame is supposed to
// be released, or exactly on the vsync period it should be released.
@@ -138,7 +137,7 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, VI::Display& display,
if (auto* layer = display.FindLayer(layer_id); layer != nullptr) {
// TODO: support release fence
// This is needed to prevent screen tearing
- layer->GetConsumer().ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
+ layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
framebuffer.is_acquired = false;
}
}
@@ -146,26 +145,26 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, VI::Display& display,
return frame_advance;
}
-void HardwareComposer::RemoveLayerLocked(VI::Display& display, LayerId layer_id) {
- // Check if we are tracking a slot with this layer_id.
- const auto it = m_framebuffers.find(layer_id);
+void HardwareComposer::RemoveLayerLocked(Display& display, ConsumerId consumer_id) {
+ // Check if we are tracking a slot with this consumer_id.
+ const auto it = m_framebuffers.find(consumer_id);
if (it == m_framebuffers.end()) {
return;
}
// Try to release the buffer item.
- auto* const layer = display.FindLayer(layer_id);
+ auto* const layer = display.FindLayer(consumer_id);
if (layer && it->second.is_acquired) {
- layer->GetConsumer().ReleaseBuffer(it->second.item, android::Fence::NoFence());
+ layer->buffer_item_consumer->ReleaseBuffer(it->second.item, android::Fence::NoFence());
}
// Erase the slot.
m_framebuffers.erase(it);
}
-bool HardwareComposer::TryAcquireFramebufferLocked(VI::Layer& layer, Framebuffer& framebuffer) {
+bool HardwareComposer::TryAcquireFramebufferLocked(Layer& layer, Framebuffer& framebuffer) {
// Attempt the update.
- const auto status = layer.GetConsumer().AcquireBuffer(&framebuffer.item, {}, false);
+ const auto status = layer.buffer_item_consumer->AcquireBuffer(&framebuffer.item, {}, false);
if (status != android::Status::NoError) {
return false;
}
@@ -178,10 +177,10 @@ bool HardwareComposer::TryAcquireFramebufferLocked(VI::Layer& layer, Framebuffer
return true;
}
-HardwareComposer::CacheStatus HardwareComposer::CacheFramebufferLocked(VI::Layer& layer,
- LayerId layer_id) {
+HardwareComposer::CacheStatus HardwareComposer::CacheFramebufferLocked(Layer& layer,
+ ConsumerId consumer_id) {
// Check if this framebuffer is already present.
- const auto it = m_framebuffers.find(layer_id);
+ const auto it = m_framebuffers.find(consumer_id);
if (it != m_framebuffers.end()) {
// If it's currently still acquired, we are done.
if (it->second.is_acquired) {
@@ -203,7 +202,7 @@ HardwareComposer::CacheStatus HardwareComposer::CacheFramebufferLocked(VI::Layer
if (this->TryAcquireFramebufferLocked(layer, framebuffer)) {
// Move the buffer item into a new slot.
- m_framebuffers.emplace(layer_id, std::move(framebuffer));
+ m_framebuffers.emplace(consumer_id, std::move(framebuffer));
// We succeeded.
return CacheStatus::BufferAcquired;
diff --git a/src/core/hle/service/nvnflinger/hardware_composer.h b/src/core/hle/service/nvnflinger/hardware_composer.h
index 28392c512..c5b830468 100644
--- a/src/core/hle/service/nvnflinger/hardware_composer.h
+++ b/src/core/hle/service/nvnflinger/hardware_composer.h
@@ -3,35 +3,29 @@
#pragma once
-#include <memory>
#include <boost/container/flat_map.hpp>
#include "core/hle/service/nvnflinger/buffer_item.h"
+#include "core/hle/service/nvnflinger/display.h"
namespace Service::Nvidia::Devices {
class nvdisp_disp0;
}
-namespace Service::VI {
-class Display;
-class Layer;
-} // namespace Service::VI
-
namespace Service::Nvnflinger {
-using LayerId = u64;
+using ConsumerId = s32;
class HardwareComposer {
public:
explicit HardwareComposer();
~HardwareComposer();
- u32 ComposeLocked(f32* out_speed_scale, VI::Display& display,
+ u32 ComposeLocked(f32* out_speed_scale, Display& display,
Nvidia::Devices::nvdisp_disp0& nvdisp);
- void RemoveLayerLocked(VI::Display& display, LayerId layer_id);
+ void RemoveLayerLocked(Display& display, ConsumerId consumer_id);
private:
- // TODO: do we want to track frame number in vi instead?
u64 m_frame_number{0};
private:
@@ -49,11 +43,11 @@ private:
CachedBufferReused,
};
- boost::container::flat_map<LayerId, Framebuffer> m_framebuffers{};
+ boost::container::flat_map<ConsumerId, Framebuffer> m_framebuffers{};
private:
- bool TryAcquireFramebufferLocked(VI::Layer& layer, Framebuffer& framebuffer);
- CacheStatus CacheFramebufferLocked(VI::Layer& layer, LayerId layer_id);
+ bool TryAcquireFramebufferLocked(Layer& layer, Framebuffer& framebuffer);
+ CacheStatus CacheFramebufferLocked(Layer& layer, ConsumerId consumer_id);
};
} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/vi/hos_binder_driver.cpp b/src/core/hle/service/nvnflinger/hos_binder_driver.cpp
index ba0317245..8629a2e89 100644
--- a/src/core/hle/service/vi/hos_binder_driver.cpp
+++ b/src/core/hle/service/nvnflinger/hos_binder_driver.cpp
@@ -3,13 +3,16 @@
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/nvnflinger/binder.h"
+#include "core/hle/service/nvnflinger/hos_binder_driver.h"
#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
-#include "core/hle/service/vi/hos_binder_driver.h"
-namespace Service::VI {
+namespace Service::Nvnflinger {
-IHOSBinderDriver::IHOSBinderDriver(Core::System& system_, Nvnflinger::HosBinderDriverServer& server)
- : ServiceFramework{system_, "IHOSBinderDriver"}, m_server(server) {
+IHOSBinderDriver::IHOSBinderDriver(Core::System& system_,
+ std::shared_ptr<HosBinderDriverServer> server,
+ std::shared_ptr<SurfaceFlinger> surface_flinger)
+ : ServiceFramework{system_, "IHOSBinderDriver"}, m_server(server),
+ m_surface_flinger(surface_flinger) {
static const FunctionInfo functions[] = {
{0, C<&IHOSBinderDriver::TransactParcel>, "TransactParcel"},
{1, C<&IHOSBinderDriver::AdjustRefcount>, "AdjustRefcount"},
@@ -21,13 +24,18 @@ IHOSBinderDriver::IHOSBinderDriver(Core::System& system_, Nvnflinger::HosBinderD
IHOSBinderDriver::~IHOSBinderDriver() = default;
-Result IHOSBinderDriver::TransactParcel(s32 binder_id, android::TransactionId transaction_id,
+Result IHOSBinderDriver::TransactParcel(s32 binder_id, u32 transaction_id,
InBuffer<BufferAttr_HipcMapAlias> parcel_data,
OutBuffer<BufferAttr_HipcMapAlias> parcel_reply,
u32 flags) {
LOG_DEBUG(Service_VI, "called. id={} transaction={}, flags={}", binder_id, transaction_id,
flags);
- m_server.TryGetProducer(binder_id)->Transact(transaction_id, flags, parcel_data, parcel_reply);
+
+ const auto binder = m_server->TryGetBinder(binder_id);
+ R_SUCCEED_IF(binder == nullptr);
+
+ binder->Transact(transaction_id, parcel_data, parcel_reply, flags);
+
R_SUCCEED();
}
@@ -39,15 +47,20 @@ Result IHOSBinderDriver::AdjustRefcount(s32 binder_id, s32 addval, s32 type) {
Result IHOSBinderDriver::GetNativeHandle(s32 binder_id, u32 type_id,
OutCopyHandle<Kernel::KReadableEvent> out_handle) {
LOG_WARNING(Service_VI, "(STUBBED) called id={}, type_id={}", binder_id, type_id);
- *out_handle = &m_server.TryGetProducer(binder_id)->GetNativeHandle();
+
+ const auto binder = m_server->TryGetBinder(binder_id);
+ R_UNLESS(binder != nullptr, ResultUnknown);
+
+ *out_handle = binder->GetNativeHandle(type_id);
+
R_SUCCEED();
}
-Result IHOSBinderDriver::TransactParcelAuto(s32 binder_id, android::TransactionId transaction_id,
+Result IHOSBinderDriver::TransactParcelAuto(s32 binder_id, u32 transaction_id,
InBuffer<BufferAttr_HipcAutoSelect> parcel_data,
OutBuffer<BufferAttr_HipcAutoSelect> parcel_reply,
u32 flags) {
R_RETURN(this->TransactParcel(binder_id, transaction_id, parcel_data, parcel_reply, flags));
}
-} // namespace Service::VI
+} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/vi/hos_binder_driver.h b/src/core/hle/service/nvnflinger/hos_binder_driver.h
index ed6e8cdbe..b7fb07bd2 100644
--- a/src/core/hle/service/vi/hos_binder_driver.h
+++ b/src/core/hle/service/nvnflinger/hos_binder_driver.h
@@ -2,29 +2,45 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/cmif_types.h"
-#include "core/hle/service/nvnflinger/binder.h"
#include "core/hle/service/service.h"
-namespace Service::VI {
+namespace Kernel {
+class KReadableEvent;
+}
+
+namespace Service::Nvnflinger {
+
+class HosBinderDriverServer;
+class SurfaceFlinger;
class IHOSBinderDriver final : public ServiceFramework<IHOSBinderDriver> {
public:
- explicit IHOSBinderDriver(Core::System& system_, Nvnflinger::HosBinderDriverServer& server);
+ explicit IHOSBinderDriver(Core::System& system_, std::shared_ptr<HosBinderDriverServer> server,
+ std::shared_ptr<SurfaceFlinger> surface_flinger);
~IHOSBinderDriver() override;
+ std::shared_ptr<SurfaceFlinger> GetSurfaceFlinger() {
+ return m_surface_flinger;
+ }
+
+ std::shared_ptr<HosBinderDriverServer> GetServer() {
+ return m_server;
+ }
+
private:
- Result TransactParcel(s32 binder_id, android::TransactionId transaction_id,
+ Result TransactParcel(s32 binder_id, u32 transaction_id,
InBuffer<BufferAttr_HipcMapAlias> parcel_data,
OutBuffer<BufferAttr_HipcMapAlias> parcel_reply, u32 flags);
Result AdjustRefcount(s32 binder_id, s32 addval, s32 type);
Result GetNativeHandle(s32 binder_id, u32 type_id,
OutCopyHandle<Kernel::KReadableEvent> out_handle);
- Result TransactParcelAuto(s32 binder_id, android::TransactionId transaction_id,
+ Result TransactParcelAuto(s32 binder_id, u32 transaction_id,
InBuffer<BufferAttr_HipcAutoSelect> parcel_data,
OutBuffer<BufferAttr_HipcAutoSelect> parcel_reply, u32 flags);
private:
- Nvnflinger::HosBinderDriverServer& m_server;
+ const std::shared_ptr<HosBinderDriverServer> m_server;
+ const std::shared_ptr<SurfaceFlinger> m_surface_flinger;
};
-} // namespace Service::VI
+} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/hos_binder_driver_server.cpp b/src/core/hle/service/nvnflinger/hos_binder_driver_server.cpp
index b86a79ec9..29addda44 100644
--- a/src/core/hle/service/nvnflinger/hos_binder_driver_server.cpp
+++ b/src/core/hle/service/nvnflinger/hos_binder_driver_server.cpp
@@ -8,26 +8,30 @@
namespace Service::Nvnflinger {
-HosBinderDriverServer::HosBinderDriverServer(Core::System& system_)
- : service_context(system_, "HosBinderDriverServer") {}
+HosBinderDriverServer::HosBinderDriverServer() = default;
+HosBinderDriverServer::~HosBinderDriverServer() = default;
-HosBinderDriverServer::~HosBinderDriverServer() {}
-
-u64 HosBinderDriverServer::RegisterProducer(std::unique_ptr<android::IBinder>&& binder) {
+s32 HosBinderDriverServer::RegisterBinder(std::shared_ptr<android::IBinder>&& binder) {
std::scoped_lock lk{lock};
last_id++;
- producers[last_id] = std::move(binder);
+ binders[last_id] = std::move(binder);
return last_id;
}
-android::IBinder* HosBinderDriverServer::TryGetProducer(u64 id) {
+void HosBinderDriverServer::UnregisterBinder(s32 binder_id) {
+ std::scoped_lock lk{lock};
+
+ binders.erase(binder_id);
+}
+
+std::shared_ptr<android::IBinder> HosBinderDriverServer::TryGetBinder(s32 id) const {
std::scoped_lock lk{lock};
- if (auto search = producers.find(id); search != producers.end()) {
- return search->second.get();
+ if (auto search = binders.find(id); search != binders.end()) {
+ return search->second;
}
return {};
diff --git a/src/core/hle/service/nvnflinger/hos_binder_driver_server.h b/src/core/hle/service/nvnflinger/hos_binder_driver_server.h
index 58bb9469a..d72b50833 100644
--- a/src/core/hle/service/nvnflinger/hos_binder_driver_server.h
+++ b/src/core/hle/service/nvnflinger/hos_binder_driver_server.h
@@ -8,7 +8,6 @@
#include <unordered_map>
#include "common/common_types.h"
-#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nvnflinger/binder.h"
namespace Core {
@@ -19,19 +18,18 @@ namespace Service::Nvnflinger {
class HosBinderDriverServer final {
public:
- explicit HosBinderDriverServer(Core::System& system_);
+ explicit HosBinderDriverServer();
~HosBinderDriverServer();
- u64 RegisterProducer(std::unique_ptr<android::IBinder>&& binder);
+ s32 RegisterBinder(std::shared_ptr<android::IBinder>&& binder);
+ void UnregisterBinder(s32 binder_id);
- android::IBinder* TryGetProducer(u64 id);
+ std::shared_ptr<android::IBinder> TryGetBinder(s32 id) const;
private:
- KernelHelpers::ServiceContext service_context;
-
- std::unordered_map<u64, std::unique_ptr<android::IBinder>> producers;
- std::mutex lock;
- u64 last_id{};
+ std::unordered_map<s32, std::shared_ptr<android::IBinder>> binders;
+ mutable std::mutex lock;
+ s32 last_id{};
};
} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index 687ccc9f9..9e3b68b8a 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -1,335 +1,24 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
-#include <algorithm>
-#include <optional>
-
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "common/microprofile.h"
-#include "common/scope_exit.h"
-#include "common/settings.h"
-#include "common/thread.h"
#include "core/core.h"
-#include "core/core_timing.h"
-#include "core/hle/kernel/k_readable_event.h"
-#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
-#include "core/hle/service/nvdrv/nvdrv.h"
-#include "core/hle/service/nvnflinger/buffer_item_consumer.h"
-#include "core/hle/service/nvnflinger/buffer_queue_core.h"
-#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
-#include "core/hle/service/nvnflinger/hardware_composer.h"
+#include "core/hle/service/nvnflinger/hos_binder_driver.h"
#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
#include "core/hle/service/nvnflinger/nvnflinger.h"
-#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
-#include "core/hle/service/vi/display/vi_display.h"
-#include "core/hle/service/vi/layer/vi_layer.h"
-#include "core/hle/service/vi/vi_results.h"
-#include "video_core/gpu.h"
-#include "video_core/host1x/host1x.h"
-#include "video_core/host1x/syncpoint_manager.h"
+#include "core/hle/service/nvnflinger/surface_flinger.h"
+#include "core/hle/service/server_manager.h"
+#include "core/hle/service/sm/sm.h"
namespace Service::Nvnflinger {
-constexpr auto frame_ns = std::chrono::nanoseconds{1000000000 / 60};
-
-void Nvnflinger::SplitVSync(std::stop_token stop_token) {
- system.RegisterHostThread();
- std::string name = "VSyncThread";
- MicroProfileOnThreadCreate(name.c_str());
-
- // Cleanup
- SCOPE_EXIT({ MicroProfileOnThreadExit(); });
-
- Common::SetCurrentThreadName(name.c_str());
- Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
-
- while (!stop_token.stop_requested()) {
- vsync_signal.Wait();
-
- const auto lock_guard = Lock();
-
- if (!is_abandoned) {
- Compose();
- }
- }
-}
-
-Nvnflinger::Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_)
- : system(system_), service_context(system_, "nvnflinger"),
- hos_binder_driver_server(hos_binder_driver_server_) {
- displays.emplace_back(0, "Default", hos_binder_driver_server, service_context, system);
- displays.emplace_back(1, "External", hos_binder_driver_server, service_context, system);
- displays.emplace_back(2, "Edid", hos_binder_driver_server, service_context, system);
- displays.emplace_back(3, "Internal", hos_binder_driver_server, service_context, system);
- displays.emplace_back(4, "Null", hos_binder_driver_server, service_context, system);
- guard = std::make_shared<std::mutex>();
-
- // Schedule the screen composition events
- multi_composition_event = Core::Timing::CreateEvent(
- "ScreenComposition",
- [this](s64 time,
- std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
- vsync_signal.Set();
- return std::chrono::nanoseconds(GetNextTicks());
- });
-
- single_composition_event = Core::Timing::CreateEvent(
- "ScreenComposition",
- [this](s64 time,
- std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
- const auto lock_guard = Lock();
- Compose();
-
- return std::chrono::nanoseconds(GetNextTicks());
- });
-
- if (system.IsMulticore()) {
- system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, multi_composition_event);
- vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); });
- } else {
- system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, single_composition_event);
- }
-}
-
-Nvnflinger::~Nvnflinger() {
- if (system.IsMulticore()) {
- system.CoreTiming().UnscheduleEvent(multi_composition_event);
- vsync_thread.request_stop();
- vsync_signal.Set();
- } else {
- system.CoreTiming().UnscheduleEvent(single_composition_event);
- }
-
- ShutdownLayers();
-
- if (nvdrv) {
- nvdrv->Close(disp_fd);
- }
-}
-
-void Nvnflinger::ShutdownLayers() {
- // Abandon consumers.
- {
- const auto lock_guard = Lock();
- for (auto& display : displays) {
- display.Abandon();
- }
-
- is_abandoned = true;
- }
-
- // Join the vsync thread, if it exists.
- vsync_thread = {};
-}
-
-void Nvnflinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
- nvdrv = std::move(instance);
- disp_fd = nvdrv->Open("/dev/nvdisp_disp0", {});
-}
-
-std::optional<u64> Nvnflinger::OpenDisplay(std::string_view name) {
- const auto lock_guard = Lock();
-
- LOG_DEBUG(Service_Nvnflinger, "Opening \"{}\" display", name);
-
- const auto itr =
- std::find_if(displays.begin(), displays.end(),
- [&](const VI::Display& display) { return display.GetName() == name; });
-
- if (itr == displays.end()) {
- return std::nullopt;
- }
-
- return itr->GetID();
-}
-
-bool Nvnflinger::CloseDisplay(u64 display_id) {
- const auto lock_guard = Lock();
- auto* const display = FindDisplay(display_id);
-
- if (display == nullptr) {
- return false;
- }
-
- display->Reset();
-
- return true;
-}
-
-std::optional<u64> Nvnflinger::CreateLayer(u64 display_id, LayerBlending blending) {
- const auto lock_guard = Lock();
- auto* const display = FindDisplay(display_id);
-
- if (display == nullptr) {
- return std::nullopt;
- }
-
- const u64 layer_id = next_layer_id++;
- CreateLayerAtId(*display, layer_id, blending);
- return layer_id;
-}
-
-void Nvnflinger::CreateLayerAtId(VI::Display& display, u64 layer_id, LayerBlending blending) {
- const auto buffer_id = next_buffer_queue_id++;
- display.CreateLayer(layer_id, buffer_id, nvdrv->container);
- display.FindLayer(layer_id)->SetBlending(blending);
-}
-
-bool Nvnflinger::OpenLayer(u64 layer_id) {
- const auto lock_guard = Lock();
-
- for (auto& display : displays) {
- if (auto* layer = display.FindLayer(layer_id); layer) {
- return layer->Open();
- }
- }
-
- return false;
-}
-
-bool Nvnflinger::CloseLayer(u64 layer_id) {
- const auto lock_guard = Lock();
-
- for (auto& display : displays) {
- if (auto* layer = display.FindLayer(layer_id); layer) {
- return layer->Close();
- }
- }
-
- return false;
-}
-
-void Nvnflinger::SetLayerVisibility(u64 layer_id, bool visible) {
- const auto lock_guard = Lock();
-
- for (auto& display : displays) {
- if (auto* layer = display.FindLayer(layer_id); layer) {
- layer->SetVisibility(visible);
- }
- }
-}
-
-void Nvnflinger::DestroyLayer(u64 layer_id) {
- const auto lock_guard = Lock();
-
- for (auto& display : displays) {
- display.DestroyLayer(layer_id);
- }
-}
-
-std::optional<u32> Nvnflinger::FindBufferQueueId(u64 display_id, u64 layer_id) {
- const auto lock_guard = Lock();
- const auto* const layer = FindLayer(display_id, layer_id);
-
- if (layer == nullptr) {
- return std::nullopt;
- }
-
- return layer->GetBinderId();
-}
-
-Result Nvnflinger::FindVsyncEvent(Kernel::KReadableEvent** out_vsync_event, u64 display_id) {
- const auto lock_guard = Lock();
- auto* const display = FindDisplay(display_id);
-
- if (display == nullptr) {
- return VI::ResultNotFound;
- }
-
- *out_vsync_event = display->GetVSyncEvent();
- return ResultSuccess;
-}
-
-VI::Display* Nvnflinger::FindDisplay(u64 display_id) {
- const auto itr =
- std::find_if(displays.begin(), displays.end(),
- [&](const VI::Display& display) { return display.GetID() == display_id; });
-
- if (itr == displays.end()) {
- return nullptr;
- }
-
- return &*itr;
-}
-
-const VI::Display* Nvnflinger::FindDisplay(u64 display_id) const {
- const auto itr =
- std::find_if(displays.begin(), displays.end(),
- [&](const VI::Display& display) { return display.GetID() == display_id; });
-
- if (itr == displays.end()) {
- return nullptr;
- }
-
- return &*itr;
-}
-
-VI::Layer* Nvnflinger::FindLayer(u64 display_id, u64 layer_id) {
- auto* const display = FindDisplay(display_id);
-
- if (display == nullptr) {
- return nullptr;
- }
-
- return display->FindLayer(layer_id);
-}
-
-void Nvnflinger::Compose() {
- for (auto& display : displays) {
- // Trigger vsync for this display at the end of drawing
- SCOPE_EXIT({ display.SignalVSyncEvent(); });
-
- // Don't do anything for displays without layers.
- if (!display.HasLayers()) {
- continue;
- }
-
- if (!system.IsPoweredOn()) {
- return; // We are likely shutting down
- }
-
- auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd);
- ASSERT(nvdisp);
-
- swap_interval = display.GetComposer().ComposeLocked(&compose_speed_scale, display, *nvdisp);
- }
-}
-
-s64 Nvnflinger::GetNextTicks() const {
- const auto& settings = Settings::values;
- auto speed_scale = 1.f;
- if (settings.use_multi_core.GetValue()) {
- if (settings.use_speed_limit.GetValue()) {
- // Scales the speed based on speed_limit setting on MC. SC is handled by
- // SpeedLimiter::DoSpeedLimiting.
- speed_scale = 100.f / settings.speed_limit.GetValue();
- } else {
- // Run at unlocked framerate.
- speed_scale = 0.01f;
- }
- }
-
- // Adjust by speed limit determined during composition.
- speed_scale /= compose_speed_scale;
-
- if (system.GetNVDECActive() && settings.use_video_framerate.GetValue()) {
- // Run at intended presentation rate during video playback.
- speed_scale = 1.f;
- }
-
- const f32 effective_fps = 60.f / static_cast<f32>(swap_interval);
- return static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
-}
-
-FbShareBufferManager& Nvnflinger::GetSystemBufferManager() {
- const auto lock_guard = Lock();
-
- if (!system_buffer_manager) {
- system_buffer_manager = std::make_unique<FbShareBufferManager>(system, *this, nvdrv);
- }
+void LoopProcess(Core::System& system) {
+ const auto binder_server = std::make_shared<HosBinderDriverServer>();
+ const auto surface_flinger = std::make_shared<SurfaceFlinger>(system, *binder_server);
- return *system_buffer_manager;
+ auto server_manager = std::make_unique<ServerManager>(system);
+ server_manager->RegisterNamedService(
+ "dispdrv", std::make_shared<IHOSBinderDriver>(system, binder_server, surface_flinger));
+ ServerManager::RunServer(std::move(server_manager));
}
} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h
index 4cf4f069d..5c41f3013 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.h
+++ b/src/core/hle/service/nvnflinger/nvnflinger.h
@@ -3,170 +3,12 @@
#pragma once
-#include <list>
-#include <memory>
-#include <mutex>
-#include <optional>
-#include <thread>
-#include <vector>
-
-#include "common/common_types.h"
-#include "common/polyfill_thread.h"
-#include "common/thread.h"
-#include "core/hle/result.h"
-#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/nvnflinger/hwc_layer.h"
-
-namespace Common {
-class Event;
-} // namespace Common
-
-namespace Core::Timing {
-class CoreTiming;
-struct EventType;
-} // namespace Core::Timing
-
-namespace Kernel {
-class KReadableEvent;
-} // namespace Kernel
-
-namespace Service::Nvidia {
-class Module;
-} // namespace Service::Nvidia
-
-namespace Service::VI {
-class Display;
-class Layer;
-} // namespace Service::VI
-
-namespace Service::android {
-class BufferQueueCore;
-class BufferQueueProducer;
-} // namespace Service::android
+namespace Core {
+class System;
+}
namespace Service::Nvnflinger {
-class FbShareBufferManager;
-class HardwareComposer;
-class HosBinderDriverServer;
-
-class Nvnflinger final {
-public:
- explicit Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_);
- ~Nvnflinger();
-
- void ShutdownLayers();
-
- /// Sets the NVDrv module instance to use to send buffers to the GPU.
- void SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance);
-
- /// Opens the specified display and returns the ID.
- ///
- /// If an invalid display name is provided, then an empty optional is returned.
- [[nodiscard]] std::optional<u64> OpenDisplay(std::string_view name);
-
- /// Closes the specified display by its ID.
- ///
- /// Returns false if an invalid display ID is provided.
- [[nodiscard]] bool CloseDisplay(u64 display_id);
-
- /// Creates a layer on the specified display and returns the layer ID.
- ///
- /// If an invalid display ID is specified, then an empty optional is returned.
- [[nodiscard]] std::optional<u64> CreateLayer(u64 display_id,
- LayerBlending blending = LayerBlending::None);
-
- /// Opens a layer on all displays for the given layer ID.
- bool OpenLayer(u64 layer_id);
-
- /// Closes a layer on all displays for the given layer ID.
- bool CloseLayer(u64 layer_id);
-
- /// Makes a layer visible on all displays for the given layer ID.
- void SetLayerVisibility(u64 layer_id, bool visible);
-
- /// Destroys the given layer ID.
- void DestroyLayer(u64 layer_id);
-
- /// Finds the buffer queue ID of the specified layer in the specified display.
- ///
- /// If an invalid display ID or layer ID is provided, then an empty optional is returned.
- [[nodiscard]] std::optional<u32> FindBufferQueueId(u64 display_id, u64 layer_id);
-
- /// Gets the vsync event for the specified display.
- ///
- /// If an invalid display ID is provided, then VI::ResultNotFound is returned.
- /// If the vsync event has already been retrieved, then VI::ResultPermissionDenied is returned.
- [[nodiscard]] Result FindVsyncEvent(Kernel::KReadableEvent** out_vsync_event, u64 display_id);
-
- /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
- /// finished.
- void Compose();
-
- [[nodiscard]] s64 GetNextTicks() const;
-
- FbShareBufferManager& GetSystemBufferManager();
-
-private:
- struct Layer {
- std::unique_ptr<android::BufferQueueCore> core;
- std::unique_ptr<android::BufferQueueProducer> producer;
- };
-
- friend class FbShareBufferManager;
-
-private:
- [[nodiscard]] std::unique_lock<std::mutex> Lock() const {
- return std::unique_lock{*guard};
- }
-
- /// Finds the display identified by the specified ID.
- [[nodiscard]] VI::Display* FindDisplay(u64 display_id);
-
- /// Finds the display identified by the specified ID.
- [[nodiscard]] const VI::Display* FindDisplay(u64 display_id) const;
-
- /// Finds the layer identified by the specified ID in the desired display.
- [[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id);
-
- /// Creates a layer with the specified layer ID in the desired display.
- void CreateLayerAtId(VI::Display& display, u64 layer_id, LayerBlending blending);
-
- void SplitVSync(std::stop_token stop_token);
-
- std::shared_ptr<Nvidia::Module> nvdrv;
- s32 disp_fd;
-
- std::list<VI::Display> displays;
-
- /// Id to use for the next layer that is created, this counter is shared among all displays.
- u64 next_layer_id = 1;
- /// Id to use for the next buffer queue that is created, this counter is shared among all
- /// layers.
- u32 next_buffer_queue_id = 1;
-
- s32 swap_interval = 1;
- f32 compose_speed_scale = 1.0f;
-
- bool is_abandoned = false;
-
- /// Event that handles screen composition.
- std::shared_ptr<Core::Timing::EventType> multi_composition_event;
- std::shared_ptr<Core::Timing::EventType> single_composition_event;
-
- std::unique_ptr<FbShareBufferManager> system_buffer_manager;
-
- std::shared_ptr<std::mutex> guard;
-
- Core::System& system;
-
- Common::Event vsync_signal;
-
- std::jthread vsync_thread;
-
- KernelHelpers::ServiceContext service_context;
-
- HosBinderDriverServer& hos_binder_driver_server;
-};
+void LoopProcess(Core::System& system);
} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/surface_flinger.cpp b/src/core/hle/service/nvnflinger/surface_flinger.cpp
new file mode 100644
index 000000000..41a705717
--- /dev/null
+++ b/src/core/hle/service/nvnflinger/surface_flinger.cpp
@@ -0,0 +1,124 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/core.h"
+#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
+#include "core/hle/service/nvdrv/nvdrv_interface.h"
+#include "core/hle/service/nvnflinger/display.h"
+#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
+#include "core/hle/service/nvnflinger/surface_flinger.h"
+#include "core/hle/service/sm/sm.h"
+
+#include "core/hle/service/nvnflinger/buffer_queue_consumer.h"
+#include "core/hle/service/nvnflinger/buffer_queue_core.h"
+#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
+
+namespace Service::Nvnflinger {
+
+SurfaceFlinger::SurfaceFlinger(Core::System& system, HosBinderDriverServer& server)
+ : m_system(system), m_server(server), m_context(m_system, "SurfaceFlinger") {
+ nvdrv = m_system.ServiceManager().GetService<Nvidia::NVDRV>("nvdrv:s", true)->GetModule();
+ disp_fd = nvdrv->Open("/dev/nvdisp_disp0", {});
+}
+
+SurfaceFlinger::~SurfaceFlinger() {
+ nvdrv->Close(disp_fd);
+}
+
+void SurfaceFlinger::AddDisplay(u64 display_id) {
+ m_displays.emplace_back(display_id);
+}
+
+void SurfaceFlinger::RemoveDisplay(u64 display_id) {
+ std::erase_if(m_displays, [&](auto& display) { return display.id == display_id; });
+}
+
+bool SurfaceFlinger::ComposeDisplay(s32* out_swap_interval, f32* out_compose_speed_scale,
+ u64 display_id) {
+ auto* const display = this->FindDisplay(display_id);
+ if (!display || !display->HasLayers()) {
+ return false;
+ }
+
+ *out_swap_interval =
+ m_composer.ComposeLocked(out_compose_speed_scale, *display,
+ *nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd));
+ return true;
+}
+
+void SurfaceFlinger::AddLayerToDisplayStack(u64 display_id, s32 consumer_binder_id) {
+ auto* const display = this->FindDisplay(display_id);
+ auto binder = std::static_pointer_cast<android::BufferQueueConsumer>(
+ m_server.TryGetBinder(consumer_binder_id));
+
+ if (!display || !binder) {
+ return;
+ }
+
+ auto buffer_item_consumer = std::make_shared<android::BufferItemConsumer>(std::move(binder));
+ buffer_item_consumer->Connect(false);
+
+ display->stack.layers.emplace_back(std::move(buffer_item_consumer), consumer_binder_id);
+}
+
+void SurfaceFlinger::RemoveLayerFromDisplayStack(u64 display_id, s32 consumer_binder_id) {
+ auto* const display = this->FindDisplay(display_id);
+ if (!display) {
+ return;
+ }
+
+ m_composer.RemoveLayerLocked(*display, consumer_binder_id);
+ std::erase_if(display->stack.layers,
+ [&](auto& layer) { return layer.consumer_id == consumer_binder_id; });
+}
+
+void SurfaceFlinger::SetLayerVisibility(s32 consumer_binder_id, bool visible) {
+ if (auto* layer = this->FindLayer(consumer_binder_id); layer != nullptr) {
+ layer->visible = visible;
+ return;
+ }
+}
+
+void SurfaceFlinger::SetLayerBlending(s32 consumer_binder_id, LayerBlending blending) {
+ if (auto* layer = this->FindLayer(consumer_binder_id); layer != nullptr) {
+ layer->blending = blending;
+ return;
+ }
+}
+
+Display* SurfaceFlinger::FindDisplay(u64 display_id) {
+ for (auto& display : m_displays) {
+ if (display.id == display_id) {
+ return &display;
+ }
+ }
+
+ return nullptr;
+}
+
+Layer* SurfaceFlinger::FindLayer(s32 consumer_binder_id) {
+ for (auto& display : m_displays) {
+ if (auto* layer = display.FindLayer(consumer_binder_id); layer != nullptr) {
+ return layer;
+ }
+ }
+
+ return nullptr;
+}
+
+void SurfaceFlinger::CreateBufferQueue(s32* out_consumer_binder_id, s32* out_producer_binder_id) {
+ auto& nvmap = nvdrv->GetContainer().GetNvMapFile();
+ auto core = std::make_shared<android::BufferQueueCore>();
+ auto producer = std::make_shared<android::BufferQueueProducer>(m_context, core, nvmap);
+ auto consumer = std::make_shared<android::BufferQueueConsumer>(core);
+
+ *out_consumer_binder_id = m_server.RegisterBinder(std::move(consumer));
+ *out_producer_binder_id = m_server.RegisterBinder(std::move(producer));
+}
+
+void SurfaceFlinger::DestroyBufferQueue(s32 consumer_binder_id, s32 producer_binder_id) {
+ m_server.UnregisterBinder(producer_binder_id);
+ m_server.UnregisterBinder(consumer_binder_id);
+}
+
+} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/surface_flinger.h b/src/core/hle/service/nvnflinger/surface_flinger.h
new file mode 100644
index 000000000..d8c53fbda
--- /dev/null
+++ b/src/core/hle/service/nvnflinger/surface_flinger.h
@@ -0,0 +1,65 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <vector>
+
+#include "common/common_types.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nvnflinger/hardware_composer.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::Nvidia {
+class Module;
+}
+
+// TODO: ISurfaceComposer
+// TODO: ISurfaceComposerClient
+
+namespace Service::Nvnflinger {
+
+struct Display;
+class HosBinderDriverServer;
+enum class LayerBlending : u32;
+struct Layer;
+
+class SurfaceFlinger {
+public:
+ explicit SurfaceFlinger(Core::System& system, HosBinderDriverServer& server);
+ ~SurfaceFlinger();
+
+ void AddDisplay(u64 display_id);
+ void RemoveDisplay(u64 display_id);
+ bool ComposeDisplay(s32* out_swap_interval, f32* out_compose_speed_scale, u64 display_id);
+
+ void AddLayerToDisplayStack(u64 display_id, s32 consumer_binder_id);
+ void RemoveLayerFromDisplayStack(u64 display_id, s32 consumer_binder_id);
+
+ void SetLayerVisibility(s32 consumer_binder_id, bool visible);
+ void SetLayerBlending(s32 consumer_binder_id, LayerBlending blending);
+
+private:
+ Display* FindDisplay(u64 display_id);
+ Layer* FindLayer(s32 consumer_binder_id);
+
+public:
+ // TODO: these don't belong here
+ void CreateBufferQueue(s32* out_consumer_binder_id, s32* out_producer_binder_id);
+ void DestroyBufferQueue(s32 consumer_binder_id, s32 producer_binder_id);
+
+private:
+ Core::System& m_system;
+ HosBinderDriverServer& m_server;
+ KernelHelpers::ServiceContext m_context;
+
+ std::vector<Display> m_displays;
+ std::shared_ptr<Nvidia::Module> nvdrv;
+ s32 disp_fd;
+ HardwareComposer m_composer;
+};
+
+} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/psc/time/static.cpp b/src/core/hle/service/psc/time/static.cpp
index 24b85cc61..9a0adb295 100644
--- a/src/core/hle/service/psc/time/static.cpp
+++ b/src/core/hle/service/psc/time/static.cpp
@@ -144,7 +144,9 @@ Result StaticService::GetStandardSteadyClockRtcValue(Out<s64> out_rtc_value) {
Result StaticService::IsStandardUserSystemClockAutomaticCorrectionEnabled(
Out<bool> out_is_enabled) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_is_enabled={}", *out_is_enabled); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_is_enabled={}", *out_is_enabled);
+ };
R_UNLESS(m_user_system_clock.IsInitialized(), ResultClockUninitialized);
@@ -180,7 +182,9 @@ Result StaticService::GetStandardUserSystemClockInitialYear(Out<s32> out_year) {
}
Result StaticService::IsStandardNetworkSystemClockAccuracySufficient(Out<bool> out_is_sufficient) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_is_sufficient={}", *out_is_sufficient); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_is_sufficient={}", *out_is_sufficient);
+ };
*out_is_sufficient = m_network_system_clock.IsAccuracySufficient();
@@ -189,7 +193,9 @@ Result StaticService::IsStandardNetworkSystemClockAccuracySufficient(Out<bool> o
Result StaticService::GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(
Out<SteadyClockTimePoint> out_time_point) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_time_point={}", *out_time_point); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_time_point={}", *out_time_point);
+ };
R_UNLESS(m_user_system_clock.IsInitialized(), ResultClockUninitialized);
@@ -200,7 +206,9 @@ Result StaticService::GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(
Result StaticService::CalculateMonotonicSystemClockBaseTimePoint(
Out<s64> out_time, const SystemClockContext& context) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. context={} out_time={}", context, *out_time); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. context={} out_time={}", context, *out_time);
+ };
R_UNLESS(m_time->m_standard_steady_clock.IsInitialized(), ResultClockUninitialized);
@@ -219,8 +227,9 @@ Result StaticService::CalculateMonotonicSystemClockBaseTimePoint(
}
Result StaticService::GetClockSnapshot(OutClockSnapshot out_snapshot, TimeType type) {
- SCOPE_EXIT(
- { LOG_DEBUG(Service_Time, "called. type={} out_snapshot={}", type, *out_snapshot); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. type={} out_snapshot={}", type, *out_snapshot);
+ };
SystemClockContext user_context{};
R_TRY(m_user_system_clock.GetContext(user_context));
@@ -234,11 +243,11 @@ Result StaticService::GetClockSnapshot(OutClockSnapshot out_snapshot, TimeType t
Result StaticService::GetClockSnapshotFromSystemClockContext(
TimeType type, OutClockSnapshot out_snapshot, const SystemClockContext& user_context,
const SystemClockContext& network_context) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
LOG_DEBUG(Service_Time,
"called. type={} user_context={} network_context={} out_snapshot={}", type,
user_context, network_context, *out_snapshot);
- });
+ };
R_RETURN(GetClockSnapshotImpl(out_snapshot, user_context, network_context, type));
}
@@ -246,9 +255,9 @@ Result StaticService::GetClockSnapshotFromSystemClockContext(
Result StaticService::CalculateStandardUserSystemClockDifferenceByUser(Out<s64> out_difference,
InClockSnapshot a,
InClockSnapshot b) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
LOG_DEBUG(Service_Time, "called. a={} b={} out_difference={}", *a, *b, *out_difference);
- });
+ };
auto diff_s =
std::chrono::seconds(b->user_context.offset) - std::chrono::seconds(a->user_context.offset);
@@ -276,7 +285,9 @@ Result StaticService::CalculateStandardUserSystemClockDifferenceByUser(Out<s64>
Result StaticService::CalculateSpanBetween(Out<s64> out_time, InClockSnapshot a,
InClockSnapshot b) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. a={} b={} out_time={}", *a, *b, *out_time); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. a={} b={} out_time={}", *a, *b, *out_time);
+ };
s64 time_s{};
auto res =
diff --git a/src/core/hle/service/psc/time/steady_clock.cpp b/src/core/hle/service/psc/time/steady_clock.cpp
index 948610a2b..78dcf532c 100644
--- a/src/core/hle/service/psc/time/steady_clock.cpp
+++ b/src/core/hle/service/psc/time/steady_clock.cpp
@@ -29,7 +29,9 @@ SteadyClock::SteadyClock(Core::System& system_, std::shared_ptr<TimeManager> man
}
Result SteadyClock::GetCurrentTimePoint(Out<SteadyClockTimePoint> out_time_point) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_time_point={}", *out_time_point); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_time_point={}", *out_time_point);
+ };
R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
ResultClockUninitialized);
@@ -38,7 +40,9 @@ Result SteadyClock::GetCurrentTimePoint(Out<SteadyClockTimePoint> out_time_point
}
Result SteadyClock::GetTestOffset(Out<s64> out_test_offset) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_test_offset={}", *out_test_offset); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_test_offset={}", *out_test_offset);
+ };
R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
ResultClockUninitialized);
@@ -59,7 +63,9 @@ Result SteadyClock::SetTestOffset(s64 test_offset) {
}
Result SteadyClock::GetRtcValue(Out<s64> out_rtc_value) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_rtc_value={}", *out_rtc_value); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_rtc_value={}", *out_rtc_value);
+ };
R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
ResultClockUninitialized);
@@ -68,7 +74,9 @@ Result SteadyClock::GetRtcValue(Out<s64> out_rtc_value) {
}
Result SteadyClock::IsRtcResetDetected(Out<bool> out_is_detected) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_is_detected={}", *out_is_detected); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_is_detected={}", *out_is_detected);
+ };
R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
ResultClockUninitialized);
@@ -78,7 +86,9 @@ Result SteadyClock::IsRtcResetDetected(Out<bool> out_is_detected) {
}
Result SteadyClock::GetSetupResultValue(Out<Result> out_result) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_result=0x{:X}", out_result->raw); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_result=0x{:X}", out_result->raw);
+ };
R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
ResultClockUninitialized);
@@ -88,8 +98,9 @@ Result SteadyClock::GetSetupResultValue(Out<Result> out_result) {
}
Result SteadyClock::GetInternalOffset(Out<s64> out_internal_offset) {
- SCOPE_EXIT(
- { LOG_DEBUG(Service_Time, "called. out_internal_offset={}", *out_internal_offset); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_internal_offset={}", *out_internal_offset);
+ };
R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
ResultClockUninitialized);
diff --git a/src/core/hle/service/psc/time/system_clock.cpp b/src/core/hle/service/psc/time/system_clock.cpp
index b4e9264d8..9f841d8e0 100644
--- a/src/core/hle/service/psc/time/system_clock.cpp
+++ b/src/core/hle/service/psc/time/system_clock.cpp
@@ -26,7 +26,9 @@ SystemClock::SystemClock(Core::System& system_, SystemClockCore& clock_core, boo
}
Result SystemClock::GetCurrentTime(Out<s64> out_time) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_time={}", *out_time); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_time={}", *out_time);
+ };
R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
ResultClockUninitialized);
@@ -45,7 +47,9 @@ Result SystemClock::SetCurrentTime(s64 time) {
}
Result SystemClock::GetSystemClockContext(Out<SystemClockContext> out_context) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_context={}", *out_context); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_context={}", *out_context);
+ };
R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
ResultClockUninitialized);
diff --git a/src/core/hle/service/psc/time/time_zone_service.cpp b/src/core/hle/service/psc/time/time_zone_service.cpp
index 2f80030a4..9e0674f27 100644
--- a/src/core/hle/service/psc/time/time_zone_service.cpp
+++ b/src/core/hle/service/psc/time/time_zone_service.cpp
@@ -37,7 +37,9 @@ TimeZoneService::TimeZoneService(Core::System& system_, StandardSteadyClockCore&
}
Result TimeZoneService::GetDeviceLocationName(Out<LocationName> out_location_name) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_location_name={}", *out_location_name); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_location_name={}", *out_location_name);
+ };
R_RETURN(m_time_zone.GetLocationName(*out_location_name));
}
@@ -50,7 +52,9 @@ Result TimeZoneService::SetDeviceLocationName(const LocationName& location_name)
}
Result TimeZoneService::GetTotalLocationNameCount(Out<u32> out_count) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_count={}", *out_count); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_count={}", *out_count);
+ };
R_RETURN(m_time_zone.GetTotalLocationCount(*out_count));
}
@@ -69,17 +73,19 @@ Result TimeZoneService::LoadTimeZoneRule(OutRule out_rule, const LocationName& l
}
Result TimeZoneService::GetTimeZoneRuleVersion(Out<RuleVersion> out_rule_version) {
- SCOPE_EXIT({ LOG_DEBUG(Service_Time, "called. out_rule_version={}", *out_rule_version); });
+ SCOPE_EXIT {
+ LOG_DEBUG(Service_Time, "called. out_rule_version={}", *out_rule_version);
+ };
R_RETURN(m_time_zone.GetRuleVersion(*out_rule_version));
}
Result TimeZoneService::GetDeviceLocationNameAndUpdatedTime(
Out<LocationName> out_location_name, Out<SteadyClockTimePoint> out_time_point) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
LOG_DEBUG(Service_Time, "called. out_location_name={} out_time_point={}",
*out_location_name, *out_time_point);
- });
+ };
R_TRY(m_time_zone.GetLocationName(*out_location_name));
R_RETURN(m_time_zone.GetTimePoint(*out_time_point));
@@ -116,10 +122,10 @@ Result TimeZoneService::GetDeviceLocationNameOperationEventReadableHandle(
Result TimeZoneService::ToCalendarTime(Out<CalendarTime> out_calendar_time,
Out<CalendarAdditionalInfo> out_additional_info, s64 time,
InRule rule) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
LOG_DEBUG(Service_Time, "called. time={} out_calendar_time={} out_additional_info={}", time,
*out_calendar_time, *out_additional_info);
- });
+ };
R_RETURN(
m_time_zone.ToCalendarTime(*out_calendar_time, *out_additional_info, time, *rule.Get()));
@@ -128,10 +134,10 @@ Result TimeZoneService::ToCalendarTime(Out<CalendarTime> out_calendar_time,
Result TimeZoneService::ToCalendarTimeWithMyRule(Out<CalendarTime> out_calendar_time,
Out<CalendarAdditionalInfo> out_additional_info,
s64 time) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
LOG_DEBUG(Service_Time, "called. time={} out_calendar_time={} out_additional_info={}", time,
*out_calendar_time, *out_additional_info);
- });
+ };
R_RETURN(m_time_zone.ToCalendarTimeWithMyRule(*out_calendar_time, *out_additional_info, time));
}
@@ -139,11 +145,11 @@ Result TimeZoneService::ToCalendarTimeWithMyRule(Out<CalendarTime> out_calendar_
Result TimeZoneService::ToPosixTime(Out<u32> out_count,
OutArray<s64, BufferAttr_HipcPointer> out_times,
const CalendarTime& calendar_time, InRule rule) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
LOG_DEBUG(Service_Time,
"called. calendar_time={} out_count={} out_times[0]={} out_times[1]={} ",
calendar_time, *out_count, out_times[0], out_times[1]);
- });
+ };
R_RETURN(
m_time_zone.ToPosixTime(*out_count, out_times, out_times.size(), calendar_time, *rule));
@@ -152,11 +158,11 @@ Result TimeZoneService::ToPosixTime(Out<u32> out_count,
Result TimeZoneService::ToPosixTimeWithMyRule(Out<u32> out_count,
OutArray<s64, BufferAttr_HipcPointer> out_times,
const CalendarTime& calendar_time) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
LOG_DEBUG(Service_Time,
"called. calendar_time={} out_count={} out_times[0]={} out_times[1]={} ",
calendar_time, *out_count, out_times[0], out_times[1]);
- });
+ };
R_RETURN(
m_time_zone.ToPosixTimeWithMyRule(*out_count, out_times, out_times.size(), calendar_time));
diff --git a/src/core/hle/service/server_manager.cpp b/src/core/hle/service/server_manager.cpp
index 8c7f94c8c..0b41bbcb9 100644
--- a/src/core/hle/service/server_manager.cpp
+++ b/src/core/hle/service/server_manager.cpp
@@ -177,10 +177,10 @@ Result ServerManager::ManageNamedPort(const std::string& service_name,
Kernel::KPort::Register(m_system.Kernel(), port);
// Ensure that our reference to the port is closed if we fail to register it.
- SCOPE_EXIT({
+ SCOPE_EXIT {
port->GetClientPort().Close();
port->GetServerPort().Close();
- });
+ };
// Register the object name with the kernel.
R_TRY(Kernel::KObjectName::NewFromName(m_system.Kernel(), std::addressof(port->GetClientPort()),
@@ -237,7 +237,9 @@ void ServerManager::StartAdditionalHostThreads(const char* name, size_t num_thre
}
Result ServerManager::LoopProcess() {
- SCOPE_EXIT({ m_stopped.Set(); });
+ SCOPE_EXIT {
+ m_stopped.Set();
+ };
R_RETURN(this->LoopProcessImpl());
}
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index fbdf217ba..ce5e3b5b4 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -7,68 +7,10 @@
#include "common/settings.h"
#include "core/core.h"
#include "core/hle/ipc.h"
-#include "core/hle/kernel/k_process.h"
-#include "core/hle/kernel/k_server_port.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/service/acc/acc.h"
-#include "core/hle/service/am/am.h"
-#include "core/hle/service/aoc/aoc_u.h"
-#include "core/hle/service/apm/apm.h"
-#include "core/hle/service/audio/audio.h"
-#include "core/hle/service/bcat/bcat.h"
-#include "core/hle/service/bpc/bpc.h"
-#include "core/hle/service/btdrv/btdrv.h"
-#include "core/hle/service/btm/btm.h"
-#include "core/hle/service/caps/caps.h"
-#include "core/hle/service/erpt/erpt.h"
-#include "core/hle/service/es/es.h"
-#include "core/hle/service/eupld/eupld.h"
-#include "core/hle/service/fatal/fatal.h"
-#include "core/hle/service/fgm/fgm.h"
-#include "core/hle/service/filesystem/filesystem.h"
-#include "core/hle/service/friend/friend.h"
-#include "core/hle/service/glue/glue.h"
-#include "core/hle/service/grc/grc.h"
-#include "core/hle/service/hid/hid.h"
#include "core/hle/service/ipc_helpers.h"
-#include "core/hle/service/jit/jit.h"
-#include "core/hle/service/lbl/lbl.h"
-#include "core/hle/service/ldn/ldn.h"
-#include "core/hle/service/ldr/ldr.h"
-#include "core/hle/service/lm/lm.h"
-#include "core/hle/service/mig/mig.h"
-#include "core/hle/service/mii/mii.h"
-#include "core/hle/service/mm/mm_u.h"
-#include "core/hle/service/mnpp/mnpp_app.h"
-#include "core/hle/service/ncm/ncm.h"
-#include "core/hle/service/nfc/nfc.h"
-#include "core/hle/service/nfp/nfp.h"
-#include "core/hle/service/ngc/ngc.h"
-#include "core/hle/service/nifm/nifm.h"
-#include "core/hle/service/nim/nim.h"
-#include "core/hle/service/npns/npns.h"
-#include "core/hle/service/ns/ns.h"
-#include "core/hle/service/nvdrv/nvdrv.h"
-#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
-#include "core/hle/service/nvnflinger/nvnflinger.h"
-#include "core/hle/service/olsc/olsc.h"
-#include "core/hle/service/omm/omm.h"
-#include "core/hle/service/pcie/pcie.h"
-#include "core/hle/service/pctl/pctl_module.h"
-#include "core/hle/service/pcv/pcv.h"
-#include "core/hle/service/pm/pm.h"
-#include "core/hle/service/prepo/prepo.h"
-#include "core/hle/service/psc/psc.h"
-#include "core/hle/service/ptm/ptm.h"
-#include "core/hle/service/ro/ro.h"
#include "core/hle/service/service.h"
-#include "core/hle/service/set/settings.h"
#include "core/hle/service/sm/sm.h"
-#include "core/hle/service/sockets/sockets.h"
-#include "core/hle/service/spl/spl_module.h"
-#include "core/hle/service/ssl/ssl.h"
-#include "core/hle/service/usb/usb.h"
-#include "core/hle/service/vi/vi.h"
#include "core/reporter.h"
namespace Service {
@@ -209,82 +151,4 @@ Result ServiceFrameworkBase::HandleSyncRequest(Kernel::KServerSession& session,
return result;
}
-/// Initialize Services
-Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system)
- : hos_binder_driver_server{std::make_unique<Nvnflinger::HosBinderDriverServer>(system)},
- nv_flinger{std::make_unique<Nvnflinger::Nvnflinger>(system, *hos_binder_driver_server)} {
-
- auto& kernel = system.Kernel();
-
- // Nvnflinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
- // here and pass it into the respective InstallInterfaces functions.
- system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false);
-
- // clang-format off
- kernel.RunOnHostCoreProcess("audio", [&] { Audio::LoopProcess(system); }).detach();
- kernel.RunOnHostCoreProcess("FS", [&] { FileSystem::LoopProcess(system); }).detach();
- kernel.RunOnHostCoreProcess("jit", [&] { JIT::LoopProcess(system); }).detach();
- kernel.RunOnHostCoreProcess("ldn", [&] { LDN::LoopProcess(system); }).detach();
- kernel.RunOnHostCoreProcess("Loader", [&] { LDR::LoopProcess(system); }).detach();
- kernel.RunOnHostCoreProcess("nvservices", [&] { Nvidia::LoopProcess(*nv_flinger, system); }).detach();
- kernel.RunOnHostCoreProcess("bsdsocket", [&] { Sockets::LoopProcess(system); }).detach();
- kernel.RunOnHostCoreProcess("vi", [&] { VI::LoopProcess(system, *nv_flinger, *hos_binder_driver_server); }).detach();
-
- kernel.RunOnGuestCoreProcess("sm", [&] { SM::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("account", [&] { Account::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("am", [&] { AM::LoopProcess(*nv_flinger, system); });
- kernel.RunOnGuestCoreProcess("aoc", [&] { AOC::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("apm", [&] { APM::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("bcat", [&] { BCAT::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("bpc", [&] { BPC::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("btdrv", [&] { BtDrv::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("btm", [&] { BTM::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("capsrv", [&] { Capture::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("erpt", [&] { ERPT::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("es", [&] { ES::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("eupld", [&] { EUPLD::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("fatal", [&] { Fatal::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("fgm", [&] { FGM::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("friends", [&] { Friend::LoopProcess(system); });
- // glue depends on settings and psc, so they must come first
- kernel.RunOnGuestCoreProcess("settings", [&] { Set::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("psc", [&] { PSC::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("glue", [&] { Glue::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("grc", [&] { GRC::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("hid", [&] { HID::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("lbl", [&] { LBL::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("LogManager.Prod", [&] { LM::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("mig", [&] { Migration::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("mii", [&] { Mii::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("mm", [&] { MM::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("mnpp", [&] { MNPP::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("ns", [&] { NS::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("olsc", [&] { OLSC::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("omm", [&] { OMM::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("pcie", [&] { PCIe::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("pctl", [&] { PCTL::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("pcv", [&] { PCV::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("prepo", [&] { PlayReport::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("ProcessManager", [&] { PM::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("ptm", [&] { PTM::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("ro", [&] { RO::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("spl", [&] { SPL::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("ssl", [&] { SSL::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("usb", [&] { USB::LoopProcess(system); });
- // clang-format on
-}
-
-Services::~Services() = default;
-
-void Services::KillNVNFlinger() {
- nv_flinger->ShutdownLayers();
-}
-
} // namespace Service
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 22d1343d5..36aae1c79 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -28,11 +28,6 @@ namespace FileSystem {
class FileSystemController;
}
-namespace Nvnflinger {
-class HosBinderDriverServer;
-class Nvnflinger;
-} // namespace Nvnflinger
-
namespace SM {
class ServiceManager;
}
@@ -236,20 +231,4 @@ private:
}
};
-/**
- * The purpose of this class is to own any objects that need to be shared across the other service
- * implementations. Will be torn down when the global system instance is shutdown.
- */
-class Services final {
-public:
- explicit Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system);
- ~Services();
-
- void KillNVNFlinger();
-
-private:
- std::unique_ptr<Nvnflinger::HosBinderDriverServer> hos_binder_driver_server;
- std::unique_ptr<Nvnflinger::Nvnflinger> nv_flinger;
-};
-
} // namespace Service
diff --git a/src/core/hle/service/services.cpp b/src/core/hle/service/services.cpp
new file mode 100644
index 000000000..d6c6eff50
--- /dev/null
+++ b/src/core/hle/service/services.cpp
@@ -0,0 +1,136 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/services.h"
+
+#include "core/hle/service/acc/acc.h"
+#include "core/hle/service/am/am.h"
+#include "core/hle/service/aoc/aoc_u.h"
+#include "core/hle/service/apm/apm.h"
+#include "core/hle/service/audio/audio.h"
+#include "core/hle/service/bcat/bcat.h"
+#include "core/hle/service/bpc/bpc.h"
+#include "core/hle/service/btdrv/btdrv.h"
+#include "core/hle/service/btm/btm.h"
+#include "core/hle/service/caps/caps.h"
+#include "core/hle/service/erpt/erpt.h"
+#include "core/hle/service/es/es.h"
+#include "core/hle/service/eupld/eupld.h"
+#include "core/hle/service/fatal/fatal.h"
+#include "core/hle/service/fgm/fgm.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/friend/friend.h"
+#include "core/hle/service/glue/glue.h"
+#include "core/hle/service/grc/grc.h"
+#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/ipc_helpers.h"
+#include "core/hle/service/jit/jit.h"
+#include "core/hle/service/lbl/lbl.h"
+#include "core/hle/service/ldn/ldn.h"
+#include "core/hle/service/ldr/ldr.h"
+#include "core/hle/service/lm/lm.h"
+#include "core/hle/service/mig/mig.h"
+#include "core/hle/service/mii/mii.h"
+#include "core/hle/service/mm/mm_u.h"
+#include "core/hle/service/mnpp/mnpp_app.h"
+#include "core/hle/service/ncm/ncm.h"
+#include "core/hle/service/nfc/nfc.h"
+#include "core/hle/service/nfp/nfp.h"
+#include "core/hle/service/ngc/ngc.h"
+#include "core/hle/service/nifm/nifm.h"
+#include "core/hle/service/nim/nim.h"
+#include "core/hle/service/npns/npns.h"
+#include "core/hle/service/ns/ns.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
+#include "core/hle/service/nvnflinger/nvnflinger.h"
+#include "core/hle/service/olsc/olsc.h"
+#include "core/hle/service/omm/omm.h"
+#include "core/hle/service/pcie/pcie.h"
+#include "core/hle/service/pctl/pctl_module.h"
+#include "core/hle/service/pcv/pcv.h"
+#include "core/hle/service/pm/pm.h"
+#include "core/hle/service/prepo/prepo.h"
+#include "core/hle/service/psc/psc.h"
+#include "core/hle/service/ptm/ptm.h"
+#include "core/hle/service/ro/ro.h"
+#include "core/hle/service/service.h"
+#include "core/hle/service/set/settings.h"
+#include "core/hle/service/sm/sm.h"
+#include "core/hle/service/sockets/sockets.h"
+#include "core/hle/service/spl/spl_module.h"
+#include "core/hle/service/ssl/ssl.h"
+#include "core/hle/service/usb/usb.h"
+#include "core/hle/service/vi/vi.h"
+
+namespace Service {
+
+Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system,
+ std::stop_token token) {
+ auto& kernel = system.Kernel();
+
+ system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false);
+
+ // clang-format off
+ kernel.RunOnHostCoreProcess("audio", [&] { Audio::LoopProcess(system); }).detach();
+ kernel.RunOnHostCoreProcess("FS", [&] { FileSystem::LoopProcess(system); }).detach();
+ kernel.RunOnHostCoreProcess("jit", [&] { JIT::LoopProcess(system); }).detach();
+ kernel.RunOnHostCoreProcess("ldn", [&] { LDN::LoopProcess(system); }).detach();
+ kernel.RunOnHostCoreProcess("Loader", [&] { LDR::LoopProcess(system); }).detach();
+ kernel.RunOnHostCoreProcess("nvservices", [&] { Nvidia::LoopProcess(system); }).detach();
+ kernel.RunOnHostCoreProcess("bsdsocket", [&] { Sockets::LoopProcess(system); }).detach();
+ kernel.RunOnHostCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }).detach();
+
+ kernel.RunOnGuestCoreProcess("sm", [&] { SM::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("account", [&] { Account::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("am", [&] { AM::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("aoc", [&] { AOC::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("apm", [&] { APM::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("bcat", [&] { BCAT::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("bpc", [&] { BPC::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("btdrv", [&] { BtDrv::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("btm", [&] { BTM::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("capsrv", [&] { Capture::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("erpt", [&] { ERPT::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("es", [&] { ES::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("eupld", [&] { EUPLD::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("fatal", [&] { Fatal::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("fgm", [&] { FGM::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("friends", [&] { Friend::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("settings", [&] { Set::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("psc", [&] { PSC::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("glue", [&] { Glue::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("grc", [&] { GRC::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("hid", [&] { HID::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("lbl", [&] { LBL::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("LogManager.Prod", [&] { LM::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("mig", [&] { Migration::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("mii", [&] { Mii::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("mm", [&] { MM::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("mnpp", [&] { MNPP::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("nvnflinger", [&] { Nvnflinger::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("ns", [&] { NS::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("olsc", [&] { OLSC::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("omm", [&] { OMM::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("pcie", [&] { PCIe::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("pctl", [&] { PCTL::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("pcv", [&] { PCV::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("prepo", [&] { PlayReport::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("ProcessManager", [&] { PM::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("ptm", [&] { PTM::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("ro", [&] { RO::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("spl", [&] { SPL::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("ssl", [&] { SSL::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("usb", [&] { USB::LoopProcess(system); });
+ // clang-format on
+}
+
+Services::~Services() = default;
+
+} // namespace Service
diff --git a/src/core/hle/service/services.h b/src/core/hle/service/services.h
new file mode 100644
index 000000000..a99fa1e53
--- /dev/null
+++ b/src/core/hle/service/services.h
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/polyfill_thread.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Service {
+
+/**
+ * The purpose of this class is to own any objects that need to be shared across the other service
+ * implementations. Will be torn down when the global system instance is shutdown.
+ */
+class Services final {
+public:
+ explicit Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system,
+ std::stop_token token);
+ ~Services();
+};
+
+} // namespace Service
diff --git a/src/core/hle/service/vi/application_display_service.cpp b/src/core/hle/service/vi/application_display_service.cpp
index 78229e30f..6b0bcb536 100644
--- a/src/core/hle/service/vi/application_display_service.cpp
+++ b/src/core/hle/service/vi/application_display_service.cpp
@@ -2,22 +2,21 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/cmif_serialization.h"
-#include "core/hle/service/nvnflinger/nvnflinger.h"
+#include "core/hle/service/nvnflinger/hos_binder_driver.h"
#include "core/hle/service/nvnflinger/parcel.h"
+#include "core/hle/service/os/event.h"
#include "core/hle/service/vi/application_display_service.h"
-#include "core/hle/service/vi/hos_binder_driver.h"
+#include "core/hle/service/vi/container.h"
#include "core/hle/service/vi/manager_display_service.h"
#include "core/hle/service/vi/system_display_service.h"
#include "core/hle/service/vi/vi_results.h"
namespace Service::VI {
-IApplicationDisplayService::IApplicationDisplayService(
- Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger,
- Nvnflinger::HosBinderDriverServer& hos_binder_driver_server)
- : ServiceFramework{system_, "IApplicationDisplayService"}, m_nvnflinger{nvnflinger},
- m_hos_binder_driver_server{hos_binder_driver_server} {
-
+IApplicationDisplayService::IApplicationDisplayService(Core::System& system_,
+ std::shared_ptr<Container> container)
+ : ServiceFramework{system_, "IApplicationDisplayService"},
+ m_container{std::move(container)}, m_context{system, "IApplicationDisplayService"} {
// clang-format off
static const FunctionInfo functions[] = {
{100, C<&IApplicationDisplayService::GetRelayService>, "GetRelayService"},
@@ -48,38 +47,41 @@ IApplicationDisplayService::IApplicationDisplayService(
}
IApplicationDisplayService::~IApplicationDisplayService() {
+ for (auto& [display_id, event] : m_display_vsync_events) {
+ m_container->UnlinkVsyncEvent(display_id, &event);
+ }
+ for (const auto layer_id : m_open_layer_ids) {
+ m_container->CloseLayer(layer_id);
+ }
for (const auto layer_id : m_stray_layer_ids) {
- m_nvnflinger.DestroyLayer(layer_id);
+ m_container->DestroyStrayLayer(layer_id);
}
}
Result IApplicationDisplayService::GetRelayService(
- Out<SharedPointer<IHOSBinderDriver>> out_relay_service) {
+ Out<SharedPointer<Nvnflinger::IHOSBinderDriver>> out_relay_service) {
LOG_WARNING(Service_VI, "(STUBBED) called");
- *out_relay_service = std::make_shared<IHOSBinderDriver>(system, m_hos_binder_driver_server);
- R_SUCCEED();
+ R_RETURN(m_container->GetBinderDriver(out_relay_service));
}
Result IApplicationDisplayService::GetSystemDisplayService(
Out<SharedPointer<ISystemDisplayService>> out_system_display_service) {
LOG_WARNING(Service_VI, "(STUBBED) called");
- *out_system_display_service = std::make_shared<ISystemDisplayService>(system, m_nvnflinger);
+ *out_system_display_service = std::make_shared<ISystemDisplayService>(system, m_container);
R_SUCCEED();
}
Result IApplicationDisplayService::GetManagerDisplayService(
Out<SharedPointer<IManagerDisplayService>> out_manager_display_service) {
LOG_WARNING(Service_VI, "(STUBBED) called");
- *out_manager_display_service = std::make_shared<IManagerDisplayService>(system, m_nvnflinger);
+ *out_manager_display_service = std::make_shared<IManagerDisplayService>(system, m_container);
R_SUCCEED();
}
Result IApplicationDisplayService::GetIndirectDisplayTransactionService(
- Out<SharedPointer<IHOSBinderDriver>> out_indirect_display_transaction_service) {
+ Out<SharedPointer<Nvnflinger::IHOSBinderDriver>> out_indirect_display_transaction_service) {
LOG_WARNING(Service_VI, "(STUBBED) called");
- *out_indirect_display_transaction_service =
- std::make_shared<IHOSBinderDriver>(system, m_hos_binder_driver_server);
- R_SUCCEED();
+ R_RETURN(m_container->GetBinderDriver(out_indirect_display_transaction_service));
}
Result IApplicationDisplayService::OpenDisplay(Out<u64> out_display_id, DisplayName display_name) {
@@ -89,14 +91,7 @@ Result IApplicationDisplayService::OpenDisplay(Out<u64> out_display_id, DisplayN
ASSERT_MSG(strcmp(display_name.data(), "Default") == 0,
"Non-default displays aren't supported yet");
- const auto display_id = m_nvnflinger.OpenDisplay(display_name.data());
- if (!display_id) {
- LOG_ERROR(Service_VI, "Display not found! display_name={}", display_name.data());
- R_THROW(VI::ResultNotFound);
- }
-
- *out_display_id = *display_id;
- R_SUCCEED();
+ R_RETURN(m_container->OpenDisplay(out_display_id, display_name));
}
Result IApplicationDisplayService::OpenDefaultDisplay(Out<u64> out_display_id) {
@@ -106,8 +101,7 @@ Result IApplicationDisplayService::OpenDefaultDisplay(Out<u64> out_display_id) {
Result IApplicationDisplayService::CloseDisplay(u64 display_id) {
LOG_DEBUG(Service_VI, "called");
- R_SUCCEED_IF(m_nvnflinger.CloseDisplay(display_id));
- R_THROW(ResultUnknown);
+ R_RETURN(m_container->CloseDisplay(display_id));
}
Result IApplicationDisplayService::SetDisplayEnabled(u32 state, u64 display_id) {
@@ -168,25 +162,19 @@ Result IApplicationDisplayService::OpenLayer(Out<u64> out_size,
LOG_DEBUG(Service_VI, "called. layer_id={}, aruid={:#x}", layer_id, aruid.pid);
- const auto display_id = m_nvnflinger.OpenDisplay(display_name.data());
- if (!display_id) {
- LOG_ERROR(Service_VI, "Layer not found! layer_id={}", layer_id);
- R_THROW(VI::ResultNotFound);
- }
+ u64 display_id;
+ R_TRY(m_container->OpenDisplay(&display_id, display_name));
- const auto buffer_queue_id = m_nvnflinger.FindBufferQueueId(*display_id, layer_id);
- if (!buffer_queue_id) {
- LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", *display_id);
- R_THROW(VI::ResultNotFound);
- }
+ s32 producer_binder_id;
+ R_TRY(m_container->OpenLayer(&producer_binder_id, layer_id, aruid.pid));
- if (!m_nvnflinger.OpenLayer(layer_id)) {
- LOG_WARNING(Service_VI, "Tried to open layer which was already open");
- R_THROW(VI::ResultOperationFailed);
+ {
+ std::scoped_lock lk{m_lock};
+ m_open_layer_ids.insert(layer_id);
}
android::OutputParcel parcel;
- parcel.WriteInterface(NativeWindow{*buffer_queue_id});
+ parcel.WriteInterface(NativeWindow{producer_binder_id});
const auto buffer = parcel.Serialize();
std::memcpy(out_native_window.data(), buffer.data(),
@@ -199,12 +187,13 @@ Result IApplicationDisplayService::OpenLayer(Out<u64> out_size,
Result IApplicationDisplayService::CloseLayer(u64 layer_id) {
LOG_DEBUG(Service_VI, "called. layer_id={}", layer_id);
- if (!m_nvnflinger.CloseLayer(layer_id)) {
- LOG_WARNING(Service_VI, "Tried to close layer which was not open");
- R_THROW(VI::ResultOperationFailed);
+ {
+ std::scoped_lock lk{m_lock};
+ R_UNLESS(m_open_layer_ids.contains(layer_id), VI::ResultNotFound);
+ m_open_layer_ids.erase(layer_id);
}
- R_SUCCEED();
+ R_RETURN(m_container->CloseLayer(layer_id));
}
Result IApplicationDisplayService::CreateStrayLayer(
@@ -212,27 +201,19 @@ Result IApplicationDisplayService::CreateStrayLayer(
u32 flags, u64 display_id) {
LOG_DEBUG(Service_VI, "called. flags={}, display_id={}", flags, display_id);
- const auto layer_id = m_nvnflinger.CreateLayer(display_id);
- if (!layer_id) {
- LOG_ERROR(Service_VI, "Layer not found! display_id={}", display_id);
- R_THROW(VI::ResultNotFound);
- }
+ s32 producer_binder_id;
+ R_TRY(m_container->CreateStrayLayer(&producer_binder_id, out_layer_id, display_id));
- m_stray_layer_ids.push_back(*layer_id);
- const auto buffer_queue_id = m_nvnflinger.FindBufferQueueId(display_id, *layer_id);
- if (!buffer_queue_id) {
- LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", display_id);
- R_THROW(VI::ResultNotFound);
- }
+ std::scoped_lock lk{m_lock};
+ m_stray_layer_ids.insert(*out_layer_id);
android::OutputParcel parcel;
- parcel.WriteInterface(NativeWindow{*buffer_queue_id});
+ parcel.WriteInterface(NativeWindow{producer_binder_id});
const auto buffer = parcel.Serialize();
std::memcpy(out_native_window.data(), buffer.data(),
std::min(out_native_window.size(), buffer.size()));
- *out_layer_id = *layer_id;
*out_size = buffer.size();
R_SUCCEED();
@@ -240,25 +221,27 @@ Result IApplicationDisplayService::CreateStrayLayer(
Result IApplicationDisplayService::DestroyStrayLayer(u64 layer_id) {
LOG_WARNING(Service_VI, "(STUBBED) called. layer_id={}", layer_id);
- m_nvnflinger.DestroyLayer(layer_id);
- R_SUCCEED();
+
+ {
+ std::scoped_lock lk{m_lock};
+ R_UNLESS(m_stray_layer_ids.contains(layer_id), VI::ResultNotFound);
+ m_stray_layer_ids.erase(layer_id);
+ }
+
+ R_RETURN(m_container->DestroyStrayLayer(layer_id));
}
Result IApplicationDisplayService::GetDisplayVsyncEvent(
OutCopyHandle<Kernel::KReadableEvent> out_vsync_event, u64 display_id) {
LOG_DEBUG(Service_VI, "called. display_id={}", display_id);
- const auto result = m_nvnflinger.FindVsyncEvent(out_vsync_event, display_id);
- if (result != ResultSuccess) {
- if (result == ResultNotFound) {
- LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id);
- }
+ std::scoped_lock lk{m_lock};
- R_THROW(result);
- }
+ auto [it, created] = m_display_vsync_events.emplace(display_id, m_context);
+ R_UNLESS(created, VI::ResultPermissionDenied);
- R_UNLESS(!m_vsync_event_fetched, VI::ResultPermissionDenied);
- m_vsync_event_fetched = true;
+ m_container->LinkVsyncEvent(display_id, &it->second);
+ *out_vsync_event = it->second.GetHandle();
R_SUCCEED();
}
diff --git a/src/core/hle/service/vi/application_display_service.h b/src/core/hle/service/vi/application_display_service.h
index 5dff4bb31..1bdeb8f84 100644
--- a/src/core/hle/service/vi/application_display_service.h
+++ b/src/core/hle/service/vi/application_display_service.h
@@ -1,7 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <map>
+#include <set>
+
#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/os/event.h"
#include "core/hle/service/service.h"
#include "core/hle/service/vi/vi_types.h"
@@ -9,26 +14,33 @@ namespace Kernel {
class KReadableEvent;
}
+namespace Service::Nvnflinger {
+class IHOSBinderDriver;
+}
+
namespace Service::VI {
-class IHOSBinderDriver;
+class Container;
class IManagerDisplayService;
class ISystemDisplayService;
class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> {
public:
- IApplicationDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger,
- Nvnflinger::HosBinderDriverServer& hos_binder_driver_server);
+ IApplicationDisplayService(Core::System& system_, std::shared_ptr<Container> container);
~IApplicationDisplayService() override;
-private:
- Result GetRelayService(Out<SharedPointer<IHOSBinderDriver>> out_relay_service);
+ std::shared_ptr<Container> GetContainer() const {
+ return m_container;
+ }
+
+public:
+ Result GetRelayService(Out<SharedPointer<Nvnflinger::IHOSBinderDriver>> out_relay_service);
Result GetSystemDisplayService(
Out<SharedPointer<ISystemDisplayService>> out_system_display_service);
Result GetManagerDisplayService(
Out<SharedPointer<IManagerDisplayService>> out_manager_display_service);
Result GetIndirectDisplayTransactionService(
- Out<SharedPointer<IHOSBinderDriver>> out_indirect_display_transaction_service);
+ Out<SharedPointer<Nvnflinger::IHOSBinderDriver>> out_indirect_display_transaction_service);
Result OpenDisplay(Out<u64> out_display_id, DisplayName display_name);
Result OpenDefaultDisplay(Out<u64> out_display_id);
Result CloseDisplay(u64 display_id);
@@ -56,9 +68,13 @@ private:
s64 width, s64 height);
private:
- Nvnflinger::Nvnflinger& m_nvnflinger;
- Nvnflinger::HosBinderDriverServer& m_hos_binder_driver_server;
- std::vector<u64> m_stray_layer_ids;
+ const std::shared_ptr<Container> m_container;
+
+ KernelHelpers::ServiceContext m_context;
+ std::mutex m_lock{};
+ std::set<u64> m_open_layer_ids{};
+ std::set<u64> m_stray_layer_ids{};
+ std::map<u64, Event> m_display_vsync_events{};
bool m_vsync_event_fetched{false};
};
diff --git a/src/core/hle/service/vi/application_root_service.cpp b/src/core/hle/service/vi/application_root_service.cpp
index 7af7f062c..7f35a048d 100644
--- a/src/core/hle/service/vi/application_root_service.cpp
+++ b/src/core/hle/service/vi/application_root_service.cpp
@@ -4,17 +4,16 @@
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/vi/application_display_service.h"
#include "core/hle/service/vi/application_root_service.h"
+#include "core/hle/service/vi/container.h"
#include "core/hle/service/vi/service_creator.h"
#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_types.h"
namespace Service::VI {
-IApplicationRootService::IApplicationRootService(
- Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger,
- Nvnflinger::HosBinderDriverServer& hos_binder_driver_server)
- : ServiceFramework{system_, "vi:u"}, m_nvnflinger{nvnflinger}, m_hos_binder_driver_server{
- hos_binder_driver_server} {
+IApplicationRootService::IApplicationRootService(Core::System& system_,
+ std::shared_ptr<Container> container)
+ : ServiceFramework{system_, "vi:u"}, m_container{std::move(container)} {
static const FunctionInfo functions[] = {
{0, C<&IApplicationRootService::GetDisplayService>, "GetDisplayService"},
{1, nullptr, "GetDisplayServiceWithProxyNameExchange"},
@@ -27,8 +26,8 @@ IApplicationRootService::~IApplicationRootService() = default;
Result IApplicationRootService::GetDisplayService(
Out<SharedPointer<IApplicationDisplayService>> out_application_display_service, Policy policy) {
LOG_DEBUG(Service_VI, "called");
- R_RETURN(GetApplicationDisplayService(out_application_display_service, system, m_nvnflinger,
- m_hos_binder_driver_server, Permission::User, policy));
+ R_RETURN(GetApplicationDisplayService(out_application_display_service, system, m_container,
+ Permission::User, policy));
}
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/application_root_service.h b/src/core/hle/service/vi/application_root_service.h
index 9dbf28cb4..15aa4483d 100644
--- a/src/core/hle/service/vi/application_root_service.h
+++ b/src/core/hle/service/vi/application_root_service.h
@@ -10,20 +10,15 @@ namespace Core {
class System;
}
-namespace Service::Nvnflinger {
-class HosBinderDriverServer;
-class Nvnflinger;
-} // namespace Service::Nvnflinger
-
namespace Service::VI {
+class Container;
class IApplicationDisplayService;
enum class Policy : u32;
class IApplicationRootService final : public ServiceFramework<IApplicationRootService> {
public:
- explicit IApplicationRootService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger,
- Nvnflinger::HosBinderDriverServer& hos_binder_driver_server);
+ explicit IApplicationRootService(Core::System& system_, std::shared_ptr<Container> container);
~IApplicationRootService() override;
private:
@@ -32,8 +27,7 @@ private:
Policy policy);
private:
- Nvnflinger::Nvnflinger& m_nvnflinger;
- Nvnflinger::HosBinderDriverServer& m_hos_binder_driver_server;
+ const std::shared_ptr<Container> m_container;
};
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/conductor.cpp b/src/core/hle/service/vi/conductor.cpp
new file mode 100644
index 000000000..c8ce4fca0
--- /dev/null
+++ b/src/core/hle/service/vi/conductor.cpp
@@ -0,0 +1,114 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/settings.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hle/service/vi/conductor.h"
+#include "core/hle/service/vi/container.h"
+#include "core/hle/service/vi/display_list.h"
+#include "core/hle/service/vi/vsync_manager.h"
+
+constexpr auto FrameNs = std::chrono::nanoseconds{1000000000 / 60};
+
+namespace Service::VI {
+
+Conductor::Conductor(Core::System& system, Container& container, DisplayList& displays)
+ : m_system(system), m_container(container) {
+ displays.ForEachDisplay([&](Display& display) {
+ m_vsync_managers.insert({display.GetId(), VsyncManager{}});
+ });
+
+ if (system.IsMulticore()) {
+ m_event = Core::Timing::CreateEvent(
+ "ScreenComposition",
+ [this](s64 time,
+ std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
+ m_signal.Set();
+ return std::chrono::nanoseconds(this->GetNextTicks());
+ });
+
+ system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event);
+ m_thread = std::jthread([this](std::stop_token token) { this->VsyncThread(token); });
+ } else {
+ m_event = Core::Timing::CreateEvent(
+ "ScreenComposition",
+ [this](s64 time,
+ std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
+ this->ProcessVsync();
+ return std::chrono::nanoseconds(this->GetNextTicks());
+ });
+
+ system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event);
+ }
+}
+
+Conductor::~Conductor() {
+ m_system.CoreTiming().UnscheduleEvent(m_event);
+
+ if (m_system.IsMulticore()) {
+ m_thread.request_stop();
+ m_signal.Set();
+ }
+}
+
+void Conductor::LinkVsyncEvent(u64 display_id, Event* event) {
+ if (auto it = m_vsync_managers.find(display_id); it != m_vsync_managers.end()) {
+ it->second.LinkVsyncEvent(event);
+ }
+}
+
+void Conductor::UnlinkVsyncEvent(u64 display_id, Event* event) {
+ if (auto it = m_vsync_managers.find(display_id); it != m_vsync_managers.end()) {
+ it->second.UnlinkVsyncEvent(event);
+ }
+}
+
+void Conductor::ProcessVsync() {
+ for (auto& [display_id, manager] : m_vsync_managers) {
+ m_container.ComposeOnDisplay(&m_swap_interval, &m_compose_speed_scale, display_id);
+ manager.SignalVsync();
+ }
+}
+
+void Conductor::VsyncThread(std::stop_token token) {
+ Common::SetCurrentThreadName("VSyncThread");
+
+ while (!token.stop_requested()) {
+ m_signal.Wait();
+
+ if (m_system.IsShuttingDown()) {
+ return;
+ }
+
+ this->ProcessVsync();
+ }
+}
+
+s64 Conductor::GetNextTicks() const {
+ const auto& settings = Settings::values;
+ auto speed_scale = 1.f;
+ if (settings.use_multi_core.GetValue()) {
+ if (settings.use_speed_limit.GetValue()) {
+ // Scales the speed based on speed_limit setting on MC. SC is handled by
+ // SpeedLimiter::DoSpeedLimiting.
+ speed_scale = 100.f / settings.speed_limit.GetValue();
+ } else {
+ // Run at unlocked framerate.
+ speed_scale = 0.01f;
+ }
+ }
+
+ // Adjust by speed limit determined during composition.
+ speed_scale /= m_compose_speed_scale;
+
+ if (m_system.GetNVDECActive() && settings.use_video_framerate.GetValue()) {
+ // Run at intended presentation rate during video playback.
+ speed_scale = 1.f;
+ }
+
+ const f32 effective_fps = 60.f / static_cast<f32>(m_swap_interval);
+ return static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
+}
+
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/conductor.h b/src/core/hle/service/vi/conductor.h
new file mode 100644
index 000000000..52e3595d2
--- /dev/null
+++ b/src/core/hle/service/vi/conductor.h
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <unordered_map>
+
+#include "common/common_types.h"
+#include "common/polyfill_thread.h"
+#include "common/thread.h"
+
+namespace Core {
+class System;
+}
+
+namespace Core::Timing {
+struct EventType;
+}
+
+namespace Service {
+class Event;
+}
+
+namespace Service::VI {
+
+class Container;
+class DisplayList;
+class VsyncManager;
+
+class Conductor {
+public:
+ explicit Conductor(Core::System& system, Container& container, DisplayList& displays);
+ ~Conductor();
+
+ void LinkVsyncEvent(u64 display_id, Event* event);
+ void UnlinkVsyncEvent(u64 display_id, Event* event);
+
+private:
+ void ProcessVsync();
+ void VsyncThread(std::stop_token token);
+ s64 GetNextTicks() const;
+
+private:
+ Core::System& m_system;
+ Container& m_container;
+ std::unordered_map<u64, VsyncManager> m_vsync_managers;
+ std::shared_ptr<Core::Timing::EventType> m_event;
+ Common::Event m_signal;
+ std::jthread m_thread;
+
+private:
+ s32 m_swap_interval = 1;
+ f32 m_compose_speed_scale = 1.0f;
+};
+
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/container.cpp b/src/core/hle/service/vi/container.cpp
new file mode 100644
index 000000000..310a207f1
--- /dev/null
+++ b/src/core/hle/service/vi/container.cpp
@@ -0,0 +1,228 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/core.h"
+#include "core/hle/service/nvdrv/nvdrv_interface.h"
+#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
+#include "core/hle/service/nvnflinger/hos_binder_driver.h"
+#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
+#include "core/hle/service/nvnflinger/surface_flinger.h"
+#include "core/hle/service/sm/sm.h"
+#include "core/hle/service/vi/container.h"
+#include "core/hle/service/vi/vi_results.h"
+
+namespace Service::VI {
+
+Container::Container(Core::System& system) {
+ m_displays.CreateDisplay(DisplayName{"Default"});
+ m_displays.CreateDisplay(DisplayName{"External"});
+ m_displays.CreateDisplay(DisplayName{"Edid"});
+ m_displays.CreateDisplay(DisplayName{"Internal"});
+ m_displays.CreateDisplay(DisplayName{"Null"});
+
+ m_binder_driver =
+ system.ServiceManager().GetService<Nvnflinger::IHOSBinderDriver>("dispdrv", true);
+ m_surface_flinger = m_binder_driver->GetSurfaceFlinger();
+
+ const auto nvdrv =
+ system.ServiceManager().GetService<Nvidia::NVDRV>("nvdrv:s", true)->GetModule();
+ m_shared_buffer_manager.emplace(system, *this, nvdrv);
+
+ m_displays.ForEachDisplay(
+ [&](auto& display) { m_surface_flinger->AddDisplay(display.GetId()); });
+
+ m_conductor.emplace(system, *this, m_displays);
+}
+
+Container::~Container() {
+ this->OnTerminate();
+}
+
+void Container::OnTerminate() {
+ std::scoped_lock lk{m_lock};
+
+ m_is_shut_down = true;
+
+ m_layers.ForEachLayer([&](auto& layer) {
+ if (layer.IsOpen()) {
+ this->DestroyBufferQueueLocked(&layer);
+ }
+ });
+
+ m_displays.ForEachDisplay(
+ [&](auto& display) { m_surface_flinger->RemoveDisplay(display.GetId()); });
+}
+
+SharedBufferManager* Container::GetSharedBufferManager() {
+ return std::addressof(*m_shared_buffer_manager);
+}
+
+Result Container::GetBinderDriver(
+ std::shared_ptr<Nvnflinger::IHOSBinderDriver>* out_binder_driver) {
+ *out_binder_driver = m_binder_driver;
+ R_SUCCEED();
+}
+
+Result Container::GetLayerProducerHandle(
+ std::shared_ptr<android::BufferQueueProducer>* out_producer, u64 layer_id) {
+ std::scoped_lock lk{m_lock};
+
+ auto* const layer = m_layers.GetLayerById(layer_id);
+ R_UNLESS(layer != nullptr, VI::ResultNotFound);
+
+ const auto binder = m_binder_driver->GetServer()->TryGetBinder(layer->GetProducerBinderId());
+ R_UNLESS(binder != nullptr, VI::ResultNotFound);
+
+ *out_producer = std::static_pointer_cast<android::BufferQueueProducer>(binder);
+ R_SUCCEED();
+}
+
+Result Container::OpenDisplay(u64* out_display_id, const DisplayName& display_name) {
+ auto* const display = m_displays.GetDisplayByName(display_name);
+ R_UNLESS(display != nullptr, VI::ResultNotFound);
+
+ *out_display_id = display->GetId();
+ R_SUCCEED();
+}
+
+Result Container::CloseDisplay(u64 display_id) {
+ R_SUCCEED();
+}
+
+Result Container::CreateManagedLayer(u64* out_layer_id, u64 display_id, u64 owner_aruid) {
+ std::scoped_lock lk{m_lock};
+ R_RETURN(this->CreateLayerLocked(out_layer_id, display_id, owner_aruid));
+}
+
+Result Container::DestroyManagedLayer(u64 layer_id) {
+ std::scoped_lock lk{m_lock};
+
+ // Try to close, if open, but don't fail if not.
+ this->CloseLayerLocked(layer_id);
+
+ R_RETURN(this->DestroyLayerLocked(layer_id));
+}
+
+Result Container::OpenLayer(s32* out_producer_binder_id, u64 layer_id, u64 aruid) {
+ std::scoped_lock lk{m_lock};
+ R_RETURN(this->OpenLayerLocked(out_producer_binder_id, layer_id, aruid));
+}
+
+Result Container::CloseLayer(u64 layer_id) {
+ std::scoped_lock lk{m_lock};
+ R_RETURN(this->CloseLayerLocked(layer_id));
+}
+
+Result Container::SetLayerVisibility(u64 layer_id, bool visible) {
+ std::scoped_lock lk{m_lock};
+
+ auto* const layer = m_layers.GetLayerById(layer_id);
+ R_UNLESS(layer != nullptr, VI::ResultNotFound);
+
+ m_surface_flinger->SetLayerVisibility(layer->GetConsumerBinderId(), visible);
+ R_SUCCEED();
+}
+
+Result Container::SetLayerBlending(u64 layer_id, bool enabled) {
+ std::scoped_lock lk{m_lock};
+
+ auto* const layer = m_layers.GetLayerById(layer_id);
+ R_UNLESS(layer != nullptr, VI::ResultNotFound);
+
+ m_surface_flinger->SetLayerBlending(layer->GetConsumerBinderId(),
+ enabled ? Nvnflinger::LayerBlending::Coverage
+ : Nvnflinger::LayerBlending::None);
+ R_SUCCEED();
+}
+
+void Container::LinkVsyncEvent(u64 display_id, Event* event) {
+ std::scoped_lock lk{m_lock};
+ m_conductor->LinkVsyncEvent(display_id, event);
+}
+
+void Container::UnlinkVsyncEvent(u64 display_id, Event* event) {
+ std::scoped_lock lk{m_lock};
+ m_conductor->UnlinkVsyncEvent(display_id, event);
+}
+
+Result Container::CreateStrayLayer(s32* out_producer_binder_id, u64* out_layer_id, u64 display_id) {
+ std::scoped_lock lk{m_lock};
+ R_TRY(this->CreateLayerLocked(out_layer_id, display_id, {}));
+ R_RETURN(this->OpenLayerLocked(out_producer_binder_id, *out_layer_id, {}));
+}
+
+Result Container::DestroyStrayLayer(u64 layer_id) {
+ std::scoped_lock lk{m_lock};
+ R_TRY(this->CloseLayerLocked(layer_id));
+ R_RETURN(this->DestroyLayerLocked(layer_id));
+}
+
+Result Container::CreateLayerLocked(u64* out_layer_id, u64 display_id, u64 owner_aruid) {
+ auto* const display = m_displays.GetDisplayById(display_id);
+ R_UNLESS(display != nullptr, VI::ResultNotFound);
+
+ auto* const layer = m_layers.CreateLayer(owner_aruid, display);
+ R_UNLESS(layer != nullptr, VI::ResultNotFound);
+
+ *out_layer_id = layer->GetId();
+ R_SUCCEED();
+}
+
+Result Container::DestroyLayerLocked(u64 layer_id) {
+ R_SUCCEED_IF(m_layers.DestroyLayer(layer_id));
+ R_THROW(VI::ResultNotFound);
+}
+
+Result Container::OpenLayerLocked(s32* out_producer_binder_id, u64 layer_id, u64 aruid) {
+ R_UNLESS(!m_is_shut_down, VI::ResultOperationFailed);
+
+ auto* const layer = m_layers.GetLayerById(layer_id);
+ R_UNLESS(layer != nullptr, VI::ResultNotFound);
+ R_UNLESS(!layer->IsOpen(), VI::ResultOperationFailed);
+ R_UNLESS(layer->GetOwnerAruid() == aruid, VI::ResultPermissionDenied);
+
+ this->CreateBufferQueueLocked(layer);
+ *out_producer_binder_id = layer->GetProducerBinderId();
+
+ R_SUCCEED();
+}
+
+Result Container::CloseLayerLocked(u64 layer_id) {
+ auto* const layer = m_layers.GetLayerById(layer_id);
+ R_UNLESS(layer != nullptr, VI::ResultNotFound);
+ R_UNLESS(layer->IsOpen(), VI::ResultOperationFailed);
+
+ this->DestroyBufferQueueLocked(layer);
+
+ R_SUCCEED();
+}
+
+void Container::CreateBufferQueueLocked(Layer* layer) {
+ s32 consumer_binder_id, producer_binder_id;
+ m_surface_flinger->CreateBufferQueue(&consumer_binder_id, &producer_binder_id);
+ layer->Open(consumer_binder_id, producer_binder_id);
+
+ if (auto* display = layer->GetDisplay(); display != nullptr) {
+ m_surface_flinger->AddLayerToDisplayStack(display->GetId(), consumer_binder_id);
+ }
+}
+
+void Container::DestroyBufferQueueLocked(Layer* layer) {
+ if (auto* display = layer->GetDisplay(); display != nullptr) {
+ m_surface_flinger->RemoveLayerFromDisplayStack(display->GetId(),
+ layer->GetConsumerBinderId());
+ }
+
+ layer->Close();
+ m_surface_flinger->DestroyBufferQueue(layer->GetConsumerBinderId(),
+ layer->GetProducerBinderId());
+}
+
+bool Container::ComposeOnDisplay(s32* out_swap_interval, f32* out_compose_speed_scale,
+ u64 display_id) {
+ std::scoped_lock lk{m_lock};
+ return m_surface_flinger->ComposeDisplay(out_swap_interval, out_compose_speed_scale,
+ display_id);
+}
+
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/container.h b/src/core/hle/service/vi/container.h
new file mode 100644
index 000000000..cd0d2ca86
--- /dev/null
+++ b/src/core/hle/service/vi/container.h
@@ -0,0 +1,92 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+#include <optional>
+
+#include "core/hle/service/vi/conductor.h"
+#include "core/hle/service/vi/display_list.h"
+#include "core/hle/service/vi/layer_list.h"
+#include "core/hle/service/vi/shared_buffer_manager.h"
+
+union Result;
+
+namespace Service::android {
+class BufferQueueProducer;
+}
+
+namespace Service::Nvnflinger {
+class IHOSBinderDriver;
+class SurfaceFlinger;
+} // namespace Service::Nvnflinger
+
+namespace Service {
+class Event;
+}
+
+namespace Service::VI {
+
+class SharedBufferManager;
+
+class Container {
+public:
+ explicit Container(Core::System& system);
+ ~Container();
+
+ void OnTerminate();
+
+ SharedBufferManager* GetSharedBufferManager();
+
+ Result GetBinderDriver(std::shared_ptr<Nvnflinger::IHOSBinderDriver>* out_binder_driver);
+ Result GetLayerProducerHandle(std::shared_ptr<android::BufferQueueProducer>* out_producer,
+ u64 layer_id);
+
+ Result OpenDisplay(u64* out_display_id, const DisplayName& display_name);
+ Result CloseDisplay(u64 display_id);
+
+ // Managed layers are created by the interaction between am and ommdisp
+ // on behalf of an applet. Their lifetime ends with the lifetime of the
+ // applet's ISelfController.
+ Result CreateManagedLayer(u64* out_layer_id, u64 display_id, u64 owner_aruid);
+ Result DestroyManagedLayer(u64 layer_id);
+ Result OpenLayer(s32* out_producer_binder_id, u64 layer_id, u64 aruid);
+ Result CloseLayer(u64 layer_id);
+
+ // Stray layers are created by non-applet sysmodules. Their lifetime ends
+ // with the lifetime of the IApplicationDisplayService which created them.
+ Result CreateStrayLayer(s32* out_producer_binder_id, u64* out_layer_id, u64 display_id);
+ Result DestroyStrayLayer(u64 layer_id);
+
+ Result SetLayerVisibility(u64 layer_id, bool visible);
+ Result SetLayerBlending(u64 layer_id, bool enabled);
+
+ void LinkVsyncEvent(u64 display_id, Event* event);
+ void UnlinkVsyncEvent(u64 display_id, Event* event);
+
+private:
+ Result CreateLayerLocked(u64* out_layer_id, u64 display_id, u64 owner_aruid);
+ Result DestroyLayerLocked(u64 layer_id);
+ Result OpenLayerLocked(s32* out_producer_binder_id, u64 layer_id, u64 aruid);
+ Result CloseLayerLocked(u64 layer_id);
+
+ void CreateBufferQueueLocked(Layer* layer);
+ void DestroyBufferQueueLocked(Layer* layer);
+
+public:
+ bool ComposeOnDisplay(s32* out_swap_interval, f32* out_compose_speed_scale, u64 display_id);
+
+private:
+ std::mutex m_lock{};
+ DisplayList m_displays{};
+ LayerList m_layers{};
+ std::shared_ptr<Nvnflinger::IHOSBinderDriver> m_binder_driver{};
+ std::shared_ptr<Nvnflinger::SurfaceFlinger> m_surface_flinger{};
+ std::optional<SharedBufferManager> m_shared_buffer_manager{};
+ std::optional<Conductor> m_conductor{};
+ bool m_is_shut_down{};
+};
+
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/display.h b/src/core/hle/service/vi/display.h
new file mode 100644
index 000000000..fceda75e3
--- /dev/null
+++ b/src/core/hle/service/vi/display.h
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/vi/vi_types.h"
+
+namespace Service::VI {
+
+class Display {
+public:
+ constexpr Display() = default;
+
+ void Initialize(u64 id, const DisplayName& display_name) {
+ m_id = id;
+ m_display_name = display_name;
+ m_is_initialized = true;
+ }
+
+ void Finalize() {
+ m_id = {};
+ m_display_name = {};
+ m_is_initialized = {};
+ }
+
+ u64 GetId() const {
+ return m_id;
+ }
+
+ const DisplayName& GetDisplayName() const {
+ return m_display_name;
+ }
+
+ bool IsInitialized() const {
+ return m_is_initialized;
+ }
+
+private:
+ u64 m_id{};
+ DisplayName m_display_name{};
+ bool m_is_initialized{};
+};
+
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp
deleted file mode 100644
index 7f2af9acc..000000000
--- a/src/core/hle/service/vi/display/vi_display.cpp
+++ /dev/null
@@ -1,143 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <algorithm>
-#include <utility>
-
-#include <fmt/format.h>
-
-#include "common/assert.h"
-#include "core/core.h"
-#include "core/hle/kernel/k_event.h"
-#include "core/hle/kernel/k_readable_event.h"
-#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/nvdrv/core/container.h"
-#include "core/hle/service/nvnflinger/buffer_item_consumer.h"
-#include "core/hle/service/nvnflinger/buffer_queue_consumer.h"
-#include "core/hle/service/nvnflinger/buffer_queue_core.h"
-#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
-#include "core/hle/service/nvnflinger/hardware_composer.h"
-#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
-#include "core/hle/service/vi/display/vi_display.h"
-#include "core/hle/service/vi/layer/vi_layer.h"
-#include "core/hle/service/vi/vi_results.h"
-
-namespace Service::VI {
-
-struct BufferQueue {
- std::shared_ptr<android::BufferQueueCore> core;
- std::unique_ptr<android::BufferQueueProducer> producer;
- std::unique_ptr<android::BufferQueueConsumer> consumer;
-};
-
-static BufferQueue CreateBufferQueue(KernelHelpers::ServiceContext& service_context,
- Service::Nvidia::NvCore::NvMap& nvmap) {
- auto buffer_queue_core = std::make_shared<android::BufferQueueCore>();
- return {
- buffer_queue_core,
- std::make_unique<android::BufferQueueProducer>(service_context, buffer_queue_core, nvmap),
- std::make_unique<android::BufferQueueConsumer>(buffer_queue_core)};
-}
-
-Display::Display(u64 id, std::string name_,
- Nvnflinger::HosBinderDriverServer& hos_binder_driver_server_,
- KernelHelpers::ServiceContext& service_context_, Core::System& system_)
- : display_id{id}, name{std::move(name_)}, hos_binder_driver_server{hos_binder_driver_server_},
- service_context{service_context_} {
- hardware_composer = std::make_unique<Nvnflinger::HardwareComposer>();
- vsync_event = service_context.CreateEvent(fmt::format("Display VSync Event {}", id));
-}
-
-Display::~Display() {
- service_context.CloseEvent(vsync_event);
-}
-
-Layer& Display::GetLayer(std::size_t index) {
- size_t i = 0;
- for (auto& layer : layers) {
- if (!layer->IsOpen() || !layer->IsVisible()) {
- continue;
- }
-
- if (i == index) {
- return *layer;
- }
-
- i++;
- }
-
- UNREACHABLE();
-}
-
-size_t Display::GetNumLayers() const {
- return std::ranges::count_if(layers, [](auto& l) { return l->IsOpen() && l->IsVisible(); });
-}
-
-Kernel::KReadableEvent* Display::GetVSyncEvent() {
- return &vsync_event->GetReadableEvent();
-}
-
-void Display::SignalVSyncEvent() {
- vsync_event->Signal();
-}
-
-void Display::CreateLayer(u64 layer_id, u32 binder_id,
- Service::Nvidia::NvCore::Container& nv_core) {
- auto [core, producer, consumer] = CreateBufferQueue(service_context, nv_core.GetNvMapFile());
-
- auto buffer_item_consumer = std::make_shared<android::BufferItemConsumer>(std::move(consumer));
- buffer_item_consumer->Connect(false);
-
- layers.emplace_back(std::make_unique<Layer>(layer_id, binder_id, *core, *producer,
- std::move(buffer_item_consumer)));
-
- if (is_abandoned) {
- this->FindLayer(layer_id)->GetConsumer().Abandon();
- }
-
- hos_binder_driver_server.RegisterProducer(std::move(producer));
-}
-
-void Display::DestroyLayer(u64 layer_id) {
- if (auto* layer = this->FindLayer(layer_id); layer != nullptr) {
- layer->GetConsumer().Abandon();
- }
-
- std::erase_if(layers,
- [layer_id](const auto& layer) { return layer->GetLayerId() == layer_id; });
-}
-
-void Display::Abandon() {
- for (auto& layer : layers) {
- layer->GetConsumer().Abandon();
- }
- is_abandoned = true;
-}
-
-Layer* Display::FindLayer(u64 layer_id) {
- const auto itr =
- std::find_if(layers.begin(), layers.end(), [layer_id](const std::unique_ptr<Layer>& layer) {
- return layer->GetLayerId() == layer_id;
- });
-
- if (itr == layers.end()) {
- return nullptr;
- }
-
- return itr->get();
-}
-
-const Layer* Display::FindLayer(u64 layer_id) const {
- const auto itr =
- std::find_if(layers.begin(), layers.end(), [layer_id](const std::unique_ptr<Layer>& layer) {
- return layer->GetLayerId() == layer_id;
- });
-
- if (itr == layers.end()) {
- return nullptr;
- }
-
- return itr->get();
-}
-
-} // namespace Service::VI
diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h
deleted file mode 100644
index 220292cff..000000000
--- a/src/core/hle/service/vi/display/vi_display.h
+++ /dev/null
@@ -1,143 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "core/hle/result.h"
-
-namespace Core {
-class System;
-}
-
-namespace Kernel {
-class KEvent;
-class KReadableEvent;
-} // namespace Kernel
-
-namespace Service::android {
-class BufferQueueProducer;
-}
-
-namespace Service::KernelHelpers {
-class ServiceContext;
-}
-
-namespace Service::Nvnflinger {
-class HardwareComposer;
-class HosBinderDriverServer;
-} // namespace Service::Nvnflinger
-
-namespace Service::Nvidia::NvCore {
-class Container;
-class NvMap;
-} // namespace Service::Nvidia::NvCore
-
-namespace Service::VI {
-
-class Layer;
-
-/// Represents a single display type
-class Display {
-public:
- YUZU_NON_COPYABLE(Display);
- YUZU_NON_MOVEABLE(Display);
-
- /// Constructs a display with a given unique ID and name.
- ///
- /// @param id The unique ID for this display.
- /// @param hos_binder_driver_server_ Nvnflinger HOSBinderDriver server instance.
- /// @param service_context_ The ServiceContext for the owning service.
- /// @param name_ The name for this display.
- /// @param system_ The global system instance.
- ///
- Display(u64 id, std::string name_, Nvnflinger::HosBinderDriverServer& hos_binder_driver_server_,
- KernelHelpers::ServiceContext& service_context_, Core::System& system_);
- ~Display();
-
- /// Gets the unique ID assigned to this display.
- u64 GetID() const {
- return display_id;
- }
-
- /// Gets the name of this display
- const std::string& GetName() const {
- return name;
- }
-
- /// Whether or not this display has any layers added to it.
- bool HasLayers() const {
- return GetNumLayers() > 0;
- }
-
- /// Gets a layer for this display based off an index.
- Layer& GetLayer(std::size_t index);
-
- std::size_t GetNumLayers() const;
-
- /// Gets the internal vsync event.
- Kernel::KReadableEvent* GetVSyncEvent();
-
- /// Signals the internal vsync event.
- void SignalVSyncEvent();
-
- /// Creates and adds a layer to this display with the given ID.
- ///
- /// @param layer_id The ID to assign to the created layer.
- /// @param binder_id The ID assigned to the buffer queue.
- ///
- void CreateLayer(u64 layer_id, u32 binder_id, Service::Nvidia::NvCore::Container& core);
-
- /// Removes a layer from this display with the given ID.
- ///
- /// @param layer_id The ID assigned to the layer to destroy.
- ///
- void DestroyLayer(u64 layer_id);
-
- /// Resets the display for a new connection.
- void Reset() {
- layers.clear();
- }
-
- void Abandon();
-
- /// Attempts to find a layer with the given ID.
- ///
- /// @param layer_id The layer ID.
- ///
- /// @returns If found, the Layer instance with the given ID.
- /// If not found, then nullptr is returned.
- ///
- Layer* FindLayer(u64 layer_id);
-
- /// Attempts to find a layer with the given ID.
- ///
- /// @param layer_id The layer ID.
- ///
- /// @returns If found, the Layer instance with the given ID.
- /// If not found, then nullptr is returned.
- ///
- const Layer* FindLayer(u64 layer_id) const;
-
- Nvnflinger::HardwareComposer& GetComposer() const {
- return *hardware_composer;
- }
-
-private:
- u64 display_id;
- std::string name;
- Nvnflinger::HosBinderDriverServer& hos_binder_driver_server;
- KernelHelpers::ServiceContext& service_context;
-
- std::vector<std::unique_ptr<Layer>> layers;
- std::unique_ptr<Nvnflinger::HardwareComposer> hardware_composer;
- Kernel::KEvent* vsync_event{};
- bool is_abandoned{};
-};
-
-} // namespace Service::VI
diff --git a/src/core/hle/service/vi/display_list.h b/src/core/hle/service/vi/display_list.h
new file mode 100644
index 000000000..f710ac472
--- /dev/null
+++ b/src/core/hle/service/vi/display_list.h
@@ -0,0 +1,83 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <cstring>
+
+#include "core/hle/service/vi/display.h"
+
+namespace Service::VI {
+
+class DisplayList {
+public:
+ constexpr DisplayList() = default;
+
+ bool CreateDisplay(const DisplayName& name) {
+ Display* const display = this->GetFreeDisplay();
+ if (!display) {
+ return false;
+ }
+
+ display->Initialize(m_next_id++, name);
+ return true;
+ }
+
+ bool DestroyDisplay(u64 display_id) {
+ Display* display = this->GetDisplayById(display_id);
+ if (!display) {
+ return false;
+ }
+
+ display->Finalize();
+ return true;
+ }
+
+ Display* GetDisplayByName(const DisplayName& name) {
+ for (auto& display : m_displays) {
+ if (display.IsInitialized() &&
+ std::strncmp(name.data(), display.GetDisplayName().data(), sizeof(DisplayName)) ==
+ 0) {
+ return &display;
+ }
+ }
+
+ return nullptr;
+ }
+
+ Display* GetDisplayById(u64 display_id) {
+ for (auto& display : m_displays) {
+ if (display.IsInitialized() && display.GetId() == display_id) {
+ return &display;
+ }
+ }
+
+ return nullptr;
+ }
+
+ template <typename F>
+ void ForEachDisplay(F&& cb) {
+ for (auto& display : m_displays) {
+ if (display.IsInitialized()) {
+ cb(display);
+ }
+ }
+ }
+
+private:
+ Display* GetFreeDisplay() {
+ for (auto& display : m_displays) {
+ if (!display.IsInitialized()) {
+ return &display;
+ }
+ }
+
+ return nullptr;
+ }
+
+private:
+ std::array<Display, 8> m_displays{};
+ u64 m_next_id{};
+};
+
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/layer.h b/src/core/hle/service/vi/layer.h
new file mode 100644
index 000000000..b85c8df61
--- /dev/null
+++ b/src/core/hle/service/vi/layer.h
@@ -0,0 +1,79 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Service::VI {
+
+class Display;
+
+class Layer {
+public:
+ constexpr Layer() = default;
+
+ void Initialize(u64 id, u64 owner_aruid, Display* display) {
+ m_id = id;
+ m_owner_aruid = owner_aruid;
+ m_display = display;
+ m_is_initialized = true;
+ }
+
+ void Finalize() {
+ m_id = {};
+ m_display = {};
+ m_is_initialized = {};
+ }
+
+ void Open(s32 consumer_binder_id, s32 producer_binder_id) {
+ m_consumer_binder_id = consumer_binder_id;
+ m_producer_binder_id = producer_binder_id;
+ m_is_open = true;
+ }
+
+ void Close() {
+ m_producer_binder_id = {};
+ m_consumer_binder_id = {};
+ m_is_open = {};
+ }
+
+ u64 GetId() const {
+ return m_id;
+ }
+
+ u64 GetOwnerAruid() const {
+ return m_owner_aruid;
+ }
+
+ Display* GetDisplay() const {
+ return m_display;
+ }
+
+ s32 GetConsumerBinderId() const {
+ return m_consumer_binder_id;
+ }
+
+ s32 GetProducerBinderId() const {
+ return m_producer_binder_id;
+ }
+
+ bool IsInitialized() const {
+ return m_is_initialized;
+ }
+
+ bool IsOpen() const {
+ return m_is_open;
+ }
+
+private:
+ u64 m_id{};
+ u64 m_owner_aruid{};
+ Display* m_display{};
+ s32 m_consumer_binder_id{};
+ s32 m_producer_binder_id{};
+ bool m_is_initialized{};
+ bool m_is_open{};
+};
+
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/layer/vi_layer.cpp b/src/core/hle/service/vi/layer/vi_layer.cpp
deleted file mode 100644
index eca35d82a..000000000
--- a/src/core/hle/service/vi/layer/vi_layer.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "core/hle/service/nvnflinger/hwc_layer.h"
-#include "core/hle/service/vi/layer/vi_layer.h"
-
-namespace Service::VI {
-
-Layer::Layer(u64 layer_id_, u32 binder_id_, android::BufferQueueCore& core_,
- android::BufferQueueProducer& binder_,
- std::shared_ptr<android::BufferItemConsumer>&& consumer_)
- : layer_id{layer_id_}, binder_id{binder_id_}, core{core_}, binder{binder_}, consumer{std::move(
- consumer_)},
- blending{Nvnflinger::LayerBlending::None}, open{false}, visible{true} {}
-
-Layer::~Layer() = default;
-
-} // namespace Service::VI
diff --git a/src/core/hle/service/vi/layer/vi_layer.h b/src/core/hle/service/vi/layer/vi_layer.h
deleted file mode 100644
index 14e229903..000000000
--- a/src/core/hle/service/vi/layer/vi_layer.h
+++ /dev/null
@@ -1,118 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <memory>
-#include <utility>
-
-#include "common/common_types.h"
-
-namespace Service::android {
-class BufferItemConsumer;
-class BufferQueueCore;
-class BufferQueueProducer;
-} // namespace Service::android
-
-namespace Service::Nvnflinger {
-enum class LayerBlending : u32;
-}
-
-namespace Service::VI {
-
-/// Represents a single display layer.
-class Layer {
-public:
- /// Constructs a layer with a given ID and buffer queue.
- ///
- /// @param layer_id_ The ID to assign to this layer.
- /// @param binder_id_ The binder ID to assign to this layer.
- /// @param binder_ The buffer producer queue for this layer to use.
- ///
- Layer(u64 layer_id_, u32 binder_id_, android::BufferQueueCore& core_,
- android::BufferQueueProducer& binder_,
- std::shared_ptr<android::BufferItemConsumer>&& consumer_);
- ~Layer();
-
- Layer(const Layer&) = delete;
- Layer& operator=(const Layer&) = delete;
-
- Layer(Layer&&) = default;
- Layer& operator=(Layer&&) = delete;
-
- /// Gets the ID for this layer.
- u64 GetLayerId() const {
- return layer_id;
- }
-
- /// Gets the binder ID for this layer.
- u32 GetBinderId() const {
- return binder_id;
- }
-
- /// Gets a reference to the buffer queue this layer is using.
- android::BufferQueueProducer& GetBufferQueue() {
- return binder;
- }
-
- /// Gets a const reference to the buffer queue this layer is using.
- const android::BufferQueueProducer& GetBufferQueue() const {
- return binder;
- }
-
- android::BufferItemConsumer& GetConsumer() {
- return *consumer;
- }
-
- const android::BufferItemConsumer& GetConsumer() const {
- return *consumer;
- }
-
- android::BufferQueueCore& Core() {
- return core;
- }
-
- const android::BufferQueueCore& Core() const {
- return core;
- }
-
- bool IsVisible() const {
- return visible;
- }
-
- void SetVisibility(bool v) {
- visible = v;
- }
-
- bool IsOpen() const {
- return open;
- }
-
- bool Close() {
- return std::exchange(open, false);
- }
-
- bool Open() {
- return !std::exchange(open, true);
- }
-
- Nvnflinger::LayerBlending GetBlending() {
- return blending;
- }
-
- void SetBlending(Nvnflinger::LayerBlending b) {
- blending = b;
- }
-
-private:
- const u64 layer_id;
- const u32 binder_id;
- android::BufferQueueCore& core;
- android::BufferQueueProducer& binder;
- std::shared_ptr<android::BufferItemConsumer> consumer;
- Service::Nvnflinger::LayerBlending blending;
- bool open;
- bool visible;
-};
-
-} // namespace Service::VI
diff --git a/src/core/hle/service/vi/layer_list.h b/src/core/hle/service/vi/layer_list.h
new file mode 100644
index 000000000..1738ede9a
--- /dev/null
+++ b/src/core/hle/service/vi/layer_list.h
@@ -0,0 +1,69 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/vi/layer.h"
+
+namespace Service::VI {
+
+class LayerList {
+public:
+ constexpr LayerList() = default;
+
+ Layer* CreateLayer(u64 owner_aruid, Display* display) {
+ Layer* const layer = GetFreeLayer();
+ if (!layer) {
+ return nullptr;
+ }
+
+ layer->Initialize(++m_next_id, owner_aruid, display);
+ return layer;
+ }
+
+ bool DestroyLayer(u64 layer_id) {
+ Layer* const layer = GetLayerById(layer_id);
+ if (!layer) {
+ return false;
+ }
+
+ layer->Finalize();
+ return true;
+ }
+
+ Layer* GetLayerById(u64 layer_id) {
+ for (auto& layer : m_layers) {
+ if (layer.IsInitialized() && layer.GetId() == layer_id) {
+ return &layer;
+ }
+ }
+
+ return nullptr;
+ }
+
+ template <typename F>
+ void ForEachLayer(F&& cb) {
+ for (auto& layer : m_layers) {
+ if (layer.IsInitialized()) {
+ cb(layer);
+ }
+ }
+ }
+
+private:
+ Layer* GetFreeLayer() {
+ for (auto& layer : m_layers) {
+ if (!layer.IsInitialized()) {
+ return &layer;
+ }
+ }
+
+ return nullptr;
+ }
+
+private:
+ std::array<Layer, 8> m_layers{};
+ u64 m_next_id{};
+};
+
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/manager_display_service.cpp b/src/core/hle/service/vi/manager_display_service.cpp
index 17f2f3b8f..9f856282e 100644
--- a/src/core/hle/service/vi/manager_display_service.cpp
+++ b/src/core/hle/service/vi/manager_display_service.cpp
@@ -2,22 +2,21 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/cmif_serialization.h"
-#include "core/hle/service/nvnflinger/nvnflinger.h"
+#include "core/hle/service/vi/container.h"
#include "core/hle/service/vi/manager_display_service.h"
-#include "core/hle/service/vi/vi_results.h"
namespace Service::VI {
IManagerDisplayService::IManagerDisplayService(Core::System& system_,
- Nvnflinger::Nvnflinger& nvnflinger)
- : ServiceFramework{system_, "IManagerDisplayService"}, m_nvnflinger{nvnflinger} {
+ std::shared_ptr<Container> container)
+ : ServiceFramework{system_, "IManagerDisplayService"}, m_container{std::move(container)} {
// clang-format off
static const FunctionInfo functions[] = {
{200, nullptr, "AllocateProcessHeapBlock"},
{201, nullptr, "FreeProcessHeapBlock"},
{1102, nullptr, "GetDisplayResolution"},
{2010, C<&IManagerDisplayService::CreateManagedLayer>, "CreateManagedLayer"},
- {2011, nullptr, "DestroyManagedLayer"},
+ {2011, C<&IManagerDisplayService::DestroyManagedLayer>, "DestroyManagedLayer"},
{2012, nullptr, "CreateStrayLayer"},
{2050, nullptr, "CreateIndirectLayer"},
{2051, nullptr, "DestroyIndirectLayer"},
@@ -102,19 +101,30 @@ IManagerDisplayService::IManagerDisplayService(Core::System& system_,
IManagerDisplayService::~IManagerDisplayService() = default;
-Result IManagerDisplayService::CreateManagedLayer(Out<u64> out_layer_id, u32 unknown,
- u64 display_id, AppletResourceUserId aruid) {
- LOG_WARNING(Service_VI, "(STUBBED) called. unknown={}, display={}, aruid={}", unknown,
- display_id, aruid.pid);
+Result IManagerDisplayService::CreateSharedLayerSession(Kernel::KProcess* owner_process,
+ u64* out_buffer_id, u64* out_layer_handle,
+ u64 display_id, bool enable_blending) {
+ R_RETURN(m_container->GetSharedBufferManager()->CreateSession(
+ owner_process, out_buffer_id, out_layer_handle, display_id, enable_blending));
+}
- const auto layer_id = m_nvnflinger.CreateLayer(display_id);
- if (!layer_id) {
- LOG_ERROR(Service_VI, "Layer not found! display={}", display_id);
- R_THROW(VI::ResultNotFound);
- }
+void IManagerDisplayService::DestroySharedLayerSession(Kernel::KProcess* owner_process) {
+ m_container->GetSharedBufferManager()->DestroySession(owner_process);
+}
- *out_layer_id = *layer_id;
- R_SUCCEED();
+Result IManagerDisplayService::SetLayerBlending(bool enabled, u64 layer_id) {
+ R_RETURN(m_container->SetLayerBlending(layer_id, enabled));
+}
+
+Result IManagerDisplayService::CreateManagedLayer(Out<u64> out_layer_id, u32 flags, u64 display_id,
+ AppletResourceUserId aruid) {
+ LOG_DEBUG(Service_VI, "called. flags={}, display={}, aruid={}", flags, display_id, aruid.pid);
+ R_RETURN(m_container->CreateManagedLayer(out_layer_id, display_id, aruid.pid));
+}
+
+Result IManagerDisplayService::DestroyManagedLayer(u64 layer_id) {
+ LOG_DEBUG(Service_VI, "called. layer_id={}", layer_id);
+ R_RETURN(m_container->DestroyManagedLayer(layer_id));
}
Result IManagerDisplayService::AddToLayerStack(u32 stack_id, u64 layer_id) {
@@ -123,8 +133,8 @@ Result IManagerDisplayService::AddToLayerStack(u32 stack_id, u64 layer_id) {
}
Result IManagerDisplayService::SetLayerVisibility(bool visible, u64 layer_id) {
- LOG_WARNING(Service_VI, "(STUBBED) called, layer_id={}, visible={}", layer_id, visible);
- R_SUCCEED();
+ LOG_DEBUG(Service_VI, "called, layer_id={}, visible={}", layer_id, visible);
+ R_RETURN(m_container->SetLayerVisibility(layer_id, visible));
}
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/manager_display_service.h b/src/core/hle/service/vi/manager_display_service.h
index 60e646ee0..b1bdf7f41 100644
--- a/src/core/hle/service/vi/manager_display_service.h
+++ b/src/core/hle/service/vi/manager_display_service.h
@@ -4,21 +4,34 @@
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"
+namespace Kernel {
+class KProcess;
+}
+
namespace Service::VI {
+class Container;
+
class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> {
public:
- explicit IManagerDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger);
+ explicit IManagerDisplayService(Core::System& system_, std::shared_ptr<Container> container);
~IManagerDisplayService() override;
-private:
- Result CreateManagedLayer(Out<u64> out_layer_id, u32 unknown, u64 display_id,
+ Result CreateSharedLayerSession(Kernel::KProcess* owner_process, u64* out_buffer_id,
+ u64* out_layer_handle, u64 display_id, bool enable_blending);
+ void DestroySharedLayerSession(Kernel::KProcess* owner_process);
+
+ Result SetLayerBlending(bool enabled, u64 layer_id);
+
+public:
+ Result CreateManagedLayer(Out<u64> out_layer_id, u32 flags, u64 display_id,
AppletResourceUserId aruid);
+ Result DestroyManagedLayer(u64 layer_id);
Result AddToLayerStack(u32 stack_id, u64 layer_id);
Result SetLayerVisibility(bool visible, u64 layer_id);
private:
- Nvnflinger::Nvnflinger& m_nvnflinger;
+ const std::shared_ptr<Container> m_container;
};
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/manager_root_service.cpp b/src/core/hle/service/vi/manager_root_service.cpp
index a7eee4f04..0f16a15b4 100644
--- a/src/core/hle/service/vi/manager_root_service.cpp
+++ b/src/core/hle/service/vi/manager_root_service.cpp
@@ -2,7 +2,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/nvnflinger/hos_binder_driver.h"
#include "core/hle/service/vi/application_display_service.h"
+#include "core/hle/service/vi/container.h"
#include "core/hle/service/vi/manager_root_service.h"
#include "core/hle/service/vi/service_creator.h"
#include "core/hle/service/vi/vi.h"
@@ -10,11 +12,9 @@
namespace Service::VI {
-IManagerRootService::IManagerRootService(
- Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger,
- Nvnflinger::HosBinderDriverServer& hos_binder_driver_server)
- : ServiceFramework{system_, "vi:m"}, m_nvnflinger{nvnflinger}, m_hos_binder_driver_server{
- hos_binder_driver_server} {
+IManagerRootService::IManagerRootService(Core::System& system_,
+ std::shared_ptr<Container> container)
+ : ServiceFramework{system_, "vi:m"}, m_container{std::move(container)} {
static const FunctionInfo functions[] = {
{2, C<&IManagerRootService::GetDisplayService>, "GetDisplayService"},
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
@@ -31,8 +31,8 @@ IManagerRootService::~IManagerRootService() = default;
Result IManagerRootService::GetDisplayService(
Out<SharedPointer<IApplicationDisplayService>> out_application_display_service, Policy policy) {
LOG_DEBUG(Service_VI, "called");
- R_RETURN(GetApplicationDisplayService(out_application_display_service, system, m_nvnflinger,
- m_hos_binder_driver_server, Permission::Manager, policy));
+ R_RETURN(GetApplicationDisplayService(out_application_display_service, system, m_container,
+ Permission::Manager, policy));
}
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/manager_root_service.h b/src/core/hle/service/vi/manager_root_service.h
index e6cb77aeb..77cd32869 100644
--- a/src/core/hle/service/vi/manager_root_service.h
+++ b/src/core/hle/service/vi/manager_root_service.h
@@ -10,29 +10,23 @@ namespace Core {
class System;
}
-namespace Service::Nvnflinger {
-class HosBinderDriverServer;
-class Nvnflinger;
-} // namespace Service::Nvnflinger
-
namespace Service::VI {
+class Container;
class IApplicationDisplayService;
enum class Policy : u32;
class IManagerRootService final : public ServiceFramework<IManagerRootService> {
public:
- explicit IManagerRootService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger,
- Nvnflinger::HosBinderDriverServer& hos_binder_driver_server);
+ explicit IManagerRootService(Core::System& system_, std::shared_ptr<Container> container);
~IManagerRootService() override;
-private:
Result GetDisplayService(
Out<SharedPointer<IApplicationDisplayService>> out_application_display_service,
Policy policy);
- Nvnflinger::Nvnflinger& m_nvnflinger;
- Nvnflinger::HosBinderDriverServer& m_hos_binder_driver_server;
+private:
+ const std::shared_ptr<Container> m_container;
};
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/service_creator.cpp b/src/core/hle/service/vi/service_creator.cpp
index 1de9d61a4..2b8e5f957 100644
--- a/src/core/hle/service/vi/service_creator.cpp
+++ b/src/core/hle/service/vi/service_creator.cpp
@@ -22,8 +22,7 @@ static bool IsValidServiceAccess(Permission permission, Policy policy) {
Result GetApplicationDisplayService(
std::shared_ptr<IApplicationDisplayService>* out_application_display_service,
- Core::System& system, Nvnflinger::Nvnflinger& nvnflinger,
- Nvnflinger::HosBinderDriverServer& hos_binder_driver_server, Permission permission,
+ Core::System& system, std::shared_ptr<Container> container, Permission permission,
Policy policy) {
if (!IsValidServiceAccess(permission, policy)) {
@@ -32,7 +31,7 @@ Result GetApplicationDisplayService(
}
*out_application_display_service =
- std::make_shared<IApplicationDisplayService>(system, nvnflinger, hos_binder_driver_server);
+ std::make_shared<IApplicationDisplayService>(system, std::move(container));
R_SUCCEED();
}
diff --git a/src/core/hle/service/vi/service_creator.h b/src/core/hle/service/vi/service_creator.h
index 8963bcd26..c6ba1797d 100644
--- a/src/core/hle/service/vi/service_creator.h
+++ b/src/core/hle/service/vi/service_creator.h
@@ -11,23 +11,18 @@ namespace Core {
class System;
}
-namespace Service::Nvnflinger {
-class HosBinderDriverServer;
-class Nvnflinger;
-} // namespace Service::Nvnflinger
-
union Result;
namespace Service::VI {
+class Container;
class IApplicationDisplayService;
enum class Permission;
enum class Policy : u32;
Result GetApplicationDisplayService(
std::shared_ptr<IApplicationDisplayService>* out_application_display_service,
- Core::System& system, Nvnflinger::Nvnflinger& nvnflinger,
- Nvnflinger::HosBinderDriverServer& hos_binder_driver_server, Permission permission,
+ Core::System& system, std::shared_ptr<Container> container, Permission permission,
Policy policy);
} // namespace Service::VI
diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp b/src/core/hle/service/vi/shared_buffer_manager.cpp
index 90f7248a0..869b18961 100644
--- a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
+++ b/src/core/hle/service/vi/shared_buffer_manager.cpp
@@ -9,15 +9,15 @@
#include "core/hle/service/nvdrv/devices/nvmap.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
-#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
#include "core/hle/service/nvnflinger/pixel_format.h"
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
-#include "core/hle/service/vi/layer/vi_layer.h"
+#include "core/hle/service/vi/container.h"
+#include "core/hle/service/vi/shared_buffer_manager.h"
#include "core/hle/service/vi/vi_results.h"
#include "video_core/gpu.h"
#include "video_core/host1x/host1x.h"
-namespace Service::Nvnflinger {
+namespace Service::VI {
namespace {
@@ -26,7 +26,6 @@ Result AllocateSharedBufferMemory(std::unique_ptr<Kernel::KPageGroup>* out_page_
using Core::Memory::YUZU_PAGESIZE;
// Allocate memory for the system shared buffer.
- // FIXME: This memory belongs to vi's .data section.
auto& kernel = system.Kernel();
// Hold a temporary page group reference while we try to map it.
@@ -204,15 +203,15 @@ void MakeGraphicBuffer(android::BufferQueueProducer& producer, u32 slot, u32 han
} // namespace
-FbShareBufferManager::FbShareBufferManager(Core::System& system, Nvnflinger& flinger,
- std::shared_ptr<Nvidia::Module> nvdrv)
- : m_system(system), m_flinger(flinger), m_nvdrv(std::move(nvdrv)) {}
+SharedBufferManager::SharedBufferManager(Core::System& system, Container& container,
+ std::shared_ptr<Nvidia::Module> nvdrv)
+ : m_system(system), m_container(container), m_nvdrv(std::move(nvdrv)) {}
-FbShareBufferManager::~FbShareBufferManager() = default;
+SharedBufferManager::~SharedBufferManager() = default;
-Result FbShareBufferManager::Initialize(Kernel::KProcess* owner_process, u64* out_buffer_id,
- u64* out_layer_handle, u64 display_id,
- LayerBlending blending) {
+Result SharedBufferManager::CreateSession(Kernel::KProcess* owner_process, u64* out_buffer_id,
+ u64* out_layer_handle, u64 display_id,
+ bool enable_blending) {
std::scoped_lock lk{m_guard};
// Ensure we haven't already created.
@@ -237,7 +236,7 @@ Result FbShareBufferManager::Initialize(Kernel::KProcess* owner_process, u64* ou
owner_process, m_system));
// Create new session.
- auto [it, was_emplaced] = m_sessions.emplace(aruid, FbShareSession{});
+ auto [it, was_emplaced] = m_sessions.emplace(aruid, SharedBufferSession{});
auto& session = it->second;
auto& container = m_nvdrv->GetContainer();
@@ -249,17 +248,18 @@ Result FbShareBufferManager::Initialize(Kernel::KProcess* owner_process, u64* ou
session.nvmap_fd, map_address, SharedBufferSize));
// Create and open a layer for the display.
- session.layer_id = m_flinger.CreateLayer(m_display_id, blending).value();
- m_flinger.OpenLayer(session.layer_id);
+ s32 producer_binder_id;
+ R_TRY(m_container.CreateStrayLayer(std::addressof(producer_binder_id),
+ std::addressof(session.layer_id), display_id));
- // Get the layer.
- VI::Layer* layer = m_flinger.FindLayer(m_display_id, session.layer_id);
- ASSERT(layer != nullptr);
+ // Configure blending.
+ R_ASSERT(m_container.SetLayerBlending(session.layer_id, enable_blending));
// Get the producer and set preallocated buffers.
- auto& producer = layer->GetBufferQueue();
- MakeGraphicBuffer(producer, 0, session.buffer_nvmap_handle);
- MakeGraphicBuffer(producer, 1, session.buffer_nvmap_handle);
+ std::shared_ptr<android::BufferQueueProducer> producer;
+ R_TRY(m_container.GetLayerProducerHandle(std::addressof(producer), session.layer_id));
+ MakeGraphicBuffer(*producer, 0, session.buffer_nvmap_handle);
+ MakeGraphicBuffer(*producer, 1, session.buffer_nvmap_handle);
// Assign outputs.
*out_buffer_id = m_buffer_id;
@@ -269,7 +269,7 @@ Result FbShareBufferManager::Initialize(Kernel::KProcess* owner_process, u64* ou
R_SUCCEED();
}
-void FbShareBufferManager::Finalize(Kernel::KProcess* owner_process) {
+void SharedBufferManager::DestroySession(Kernel::KProcess* owner_process) {
std::scoped_lock lk{m_guard};
if (m_buffer_id == 0) {
@@ -285,7 +285,7 @@ void FbShareBufferManager::Finalize(Kernel::KProcess* owner_process) {
auto& session = it->second;
// Destroy the layer.
- m_flinger.DestroyLayer(session.layer_id);
+ R_ASSERT(m_container.DestroyStrayLayer(session.layer_id));
// Close nvmap handle.
FreeHandle(session.buffer_nvmap_handle, *m_nvdrv, session.nvmap_fd);
@@ -301,11 +301,11 @@ void FbShareBufferManager::Finalize(Kernel::KProcess* owner_process) {
m_sessions.erase(it);
}
-Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size,
- s32* out_nvmap_handle,
- SharedMemoryPoolLayout* out_pool_layout,
- u64 buffer_id,
- u64 applet_resource_user_id) {
+Result SharedBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size,
+ s32* out_nvmap_handle,
+ SharedMemoryPoolLayout* out_pool_layout,
+ u64 buffer_id,
+ u64 applet_resource_user_id) {
std::scoped_lock lk{m_guard};
R_UNLESS(m_buffer_id > 0, VI::ResultNotFound);
@@ -319,36 +319,20 @@ Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size,
R_SUCCEED();
}
-Result FbShareBufferManager::GetLayerFromId(VI::Layer** out_layer, u64 layer_id) {
- // Ensure the layer id is valid.
- R_UNLESS(layer_id > 0, VI::ResultNotFound);
-
- // Get the layer.
- VI::Layer* layer = m_flinger.FindLayer(m_display_id, layer_id);
- R_UNLESS(layer != nullptr, VI::ResultNotFound);
-
- // We succeeded.
- *out_layer = layer;
- R_SUCCEED();
-}
-
-Result FbShareBufferManager::AcquireSharedFrameBuffer(android::Fence* out_fence,
- std::array<s32, 4>& out_slot_indexes,
- s64* out_target_slot, u64 layer_id) {
+Result SharedBufferManager::AcquireSharedFrameBuffer(android::Fence* out_fence,
+ std::array<s32, 4>& out_slot_indexes,
+ s64* out_target_slot, u64 layer_id) {
std::scoped_lock lk{m_guard};
- // Get the layer.
- VI::Layer* layer;
- R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id));
-
// Get the producer.
- auto& producer = layer->GetBufferQueue();
+ std::shared_ptr<android::BufferQueueProducer> producer;
+ R_TRY(m_container.GetLayerProducerHandle(std::addressof(producer), layer_id));
// Get the next buffer from the producer.
s32 slot;
- R_UNLESS(producer.DequeueBuffer(std::addressof(slot), out_fence, SharedBufferAsync != 0,
- SharedBufferWidth, SharedBufferHeight,
- SharedBufferBlockLinearFormat, 0) == android::Status::NoError,
+ R_UNLESS(producer->DequeueBuffer(std::addressof(slot), out_fence, SharedBufferAsync != 0,
+ SharedBufferWidth, SharedBufferHeight,
+ SharedBufferBlockLinearFormat, 0) == android::Status::NoError,
VI::ResultOperationFailed);
// Assign remaining outputs.
@@ -359,27 +343,24 @@ Result FbShareBufferManager::AcquireSharedFrameBuffer(android::Fence* out_fence,
R_SUCCEED();
}
-Result FbShareBufferManager::PresentSharedFrameBuffer(android::Fence fence,
- Common::Rectangle<s32> crop_region,
- u32 transform, s32 swap_interval,
- u64 layer_id, s64 slot) {
+Result SharedBufferManager::PresentSharedFrameBuffer(android::Fence fence,
+ Common::Rectangle<s32> crop_region,
+ u32 transform, s32 swap_interval, u64 layer_id,
+ s64 slot) {
std::scoped_lock lk{m_guard};
- // Get the layer.
- VI::Layer* layer;
- R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id));
-
// Get the producer.
- auto& producer = layer->GetBufferQueue();
+ std::shared_ptr<android::BufferQueueProducer> producer;
+ R_TRY(m_container.GetLayerProducerHandle(std::addressof(producer), layer_id));
// Request to queue the buffer.
std::shared_ptr<android::GraphicBuffer> buffer;
- R_UNLESS(producer.RequestBuffer(static_cast<s32>(slot), std::addressof(buffer)) ==
+ R_UNLESS(producer->RequestBuffer(static_cast<s32>(slot), std::addressof(buffer)) ==
android::Status::NoError,
VI::ResultOperationFailed);
ON_RESULT_FAILURE {
- producer.CancelBuffer(static_cast<s32>(slot), fence);
+ producer->CancelBuffer(static_cast<s32>(slot), fence);
};
// Queue the buffer to the producer.
@@ -389,7 +370,7 @@ Result FbShareBufferManager::PresentSharedFrameBuffer(android::Fence fence,
input.fence = fence;
input.transform = static_cast<android::NativeWindowTransform>(transform);
input.swap_interval = swap_interval;
- R_UNLESS(producer.QueueBuffer(static_cast<s32>(slot), input, std::addressof(output)) ==
+ R_UNLESS(producer->QueueBuffer(static_cast<s32>(slot), input, std::addressof(output)) ==
android::Status::NoError,
VI::ResultOperationFailed);
@@ -397,25 +378,36 @@ Result FbShareBufferManager::PresentSharedFrameBuffer(android::Fence fence,
R_SUCCEED();
}
-Result FbShareBufferManager::GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event,
- u64 layer_id) {
+Result SharedBufferManager::CancelSharedFrameBuffer(u64 layer_id, s64 slot) {
std::scoped_lock lk{m_guard};
- // Get the layer.
- VI::Layer* layer;
- R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id));
+ // Get the producer.
+ std::shared_ptr<android::BufferQueueProducer> producer;
+ R_TRY(m_container.GetLayerProducerHandle(std::addressof(producer), layer_id));
+
+ // Cancel.
+ producer->CancelBuffer(static_cast<s32>(slot), android::Fence::NoFence());
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result SharedBufferManager::GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event,
+ u64 layer_id) {
+ std::scoped_lock lk{m_guard};
// Get the producer.
- auto& producer = layer->GetBufferQueue();
+ std::shared_ptr<android::BufferQueueProducer> producer;
+ R_TRY(m_container.GetLayerProducerHandle(std::addressof(producer), layer_id));
// Set the event.
- *out_event = std::addressof(producer.GetNativeHandle());
+ *out_event = producer->GetNativeHandle({});
// We succeeded.
R_SUCCEED();
}
-Result FbShareBufferManager::WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index) {
+Result SharedBufferManager::WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index) {
std::vector<u8> capture_buffer(m_system.GPU().GetAppletCaptureBuffer());
Common::ScratchBuffer<u32> scratch;
@@ -444,4 +436,4 @@ Result FbShareBufferManager::WriteAppletCaptureBuffer(bool* out_was_written, s32
R_SUCCEED();
}
-} // namespace Service::Nvnflinger
+} // namespace Service::VI
diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h b/src/core/hle/service/vi/shared_buffer_manager.h
index b79a7d23a..7c9bb7199 100644
--- a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
+++ b/src/core/hle/service/vi/shared_buffer_manager.h
@@ -8,15 +8,27 @@
#include "common/math_util.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/nvdata.h"
-#include "core/hle/service/nvnflinger/hwc_layer.h"
#include "core/hle/service/nvnflinger/nvnflinger.h"
#include "core/hle/service/nvnflinger/ui/fence.h"
namespace Kernel {
class KPageGroup;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Service::android {
+class BufferQueueProducer;
+}
+
+namespace Service::Nvidia {
+class Module;
}
-namespace Service::Nvnflinger {
+union Result;
+
+namespace Service::VI {
+
+class Container;
struct SharedMemorySlot {
u64 buffer_offset;
@@ -32,17 +44,17 @@ struct SharedMemoryPoolLayout {
};
static_assert(sizeof(SharedMemoryPoolLayout) == 0x188, "SharedMemoryPoolLayout has wrong size");
-struct FbShareSession;
+struct SharedBufferSession;
-class FbShareBufferManager final {
+class SharedBufferManager final {
public:
- explicit FbShareBufferManager(Core::System& system, Nvnflinger& flinger,
- std::shared_ptr<Nvidia::Module> nvdrv);
- ~FbShareBufferManager();
+ explicit SharedBufferManager(Core::System& system, Container& container,
+ std::shared_ptr<Nvidia::Module> nvdrv);
+ ~SharedBufferManager();
- Result Initialize(Kernel::KProcess* owner_process, u64* out_buffer_id, u64* out_layer_handle,
- u64 display_id, LayerBlending blending);
- void Finalize(Kernel::KProcess* owner_process);
+ Result CreateSession(Kernel::KProcess* owner_process, u64* out_buffer_id, u64* out_layer_handle,
+ u64 display_id, bool enable_blending);
+ void DestroySession(Kernel::KProcess* owner_process);
Result GetSharedBufferMemoryHandleId(u64* out_buffer_size, s32* out_nvmap_handle,
SharedMemoryPoolLayout* out_pool_layout, u64 buffer_id,
@@ -51,32 +63,30 @@ public:
s64* out_target_slot, u64 layer_id);
Result PresentSharedFrameBuffer(android::Fence fence, Common::Rectangle<s32> crop_region,
u32 transform, s32 swap_interval, u64 layer_id, s64 slot);
+ Result CancelSharedFrameBuffer(u64 layer_id, s64 slot);
Result GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, u64 layer_id);
Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index);
private:
- Result GetLayerFromId(VI::Layer** out_layer, u64 layer_id);
-
-private:
u64 m_next_buffer_id = 1;
u64 m_display_id = 0;
u64 m_buffer_id = 0;
SharedMemoryPoolLayout m_pool_layout = {};
- std::map<u64, FbShareSession> m_sessions;
+ std::map<u64, SharedBufferSession> m_sessions;
std::unique_ptr<Kernel::KPageGroup> m_buffer_page_group;
std::mutex m_guard;
Core::System& m_system;
- Nvnflinger& m_flinger;
- std::shared_ptr<Nvidia::Module> m_nvdrv;
+ Container& m_container;
+ const std::shared_ptr<Nvidia::Module> m_nvdrv;
};
-struct FbShareSession {
+struct SharedBufferSession {
Nvidia::DeviceFD nvmap_fd = {};
Nvidia::NvCore::SessionId session_id = {};
u64 layer_id = {};
u32 buffer_nvmap_handle = 0;
};
-} // namespace Service::Nvnflinger
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/system_display_service.cpp b/src/core/hle/service/vi/system_display_service.cpp
index 1e1cfc817..c3c50b07b 100644
--- a/src/core/hle/service/vi/system_display_service.cpp
+++ b/src/core/hle/service/vi/system_display_service.cpp
@@ -3,15 +3,15 @@
#include "common/settings.h"
#include "core/hle/service/cmif_serialization.h"
-#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
+#include "core/hle/service/vi/container.h"
#include "core/hle/service/vi/system_display_service.h"
#include "core/hle/service/vi/vi_types.h"
namespace Service::VI {
ISystemDisplayService::ISystemDisplayService(Core::System& system_,
- Nvnflinger::Nvnflinger& nvnflinger)
- : ServiceFramework{system_, "ISystemDisplayService"}, m_nvnflinger{nvnflinger} {
+ std::shared_ptr<Container> container)
+ : ServiceFramework{system_, "ISystemDisplayService"}, m_container{std::move(container)} {
// clang-format off
static const FunctionInfo functions[] = {
{1200, nullptr, "GetZOrderCountMin"},
@@ -29,7 +29,7 @@ ISystemDisplayService::ISystemDisplayService(Core::System& system_,
{2400, nullptr, "OpenIndirectLayer"},
{2401, nullptr, "CloseIndirectLayer"},
{2402, nullptr, "FlipIndirectLayer"},
- {3000, nullptr, "ListDisplayModes"},
+ {3000, C<&ISystemDisplayService::ListDisplayModes>, "ListDisplayModes"},
{3001, nullptr, "ListDisplayRgbRanges"},
{3002, nullptr, "ListDisplayContentTypes"},
{3200, C<&ISystemDisplayService::GetDisplayMode>, "GetDisplayMode"},
@@ -59,7 +59,7 @@ ISystemDisplayService::ISystemDisplayService(Core::System& system_,
{8255, C<&ISystemDisplayService::PresentSharedFrameBuffer>, "PresentSharedFrameBuffer"},
{8256, C<&ISystemDisplayService::GetSharedFrameBufferAcquirableEvent>, "GetSharedFrameBufferAcquirableEvent"},
{8257, nullptr, "FillSharedFrameBufferColor"},
- {8258, nullptr, "CancelSharedFrameBuffer"},
+ {8258, C<&ISystemDisplayService::CancelSharedFrameBuffer>, "CancelSharedFrameBuffer"},
{9000, nullptr, "GetDp2hdmiController"},
};
// clang-format on
@@ -80,31 +80,50 @@ Result ISystemDisplayService::SetLayerVisibility(bool visible, u64 layer_id) {
R_SUCCEED();
}
-Result ISystemDisplayService::GetDisplayMode(Out<u32> out_width, Out<u32> out_height,
- Out<f32> out_refresh_rate, Out<u32> out_unknown) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
+Result ISystemDisplayService::ListDisplayModes(
+ Out<u64> out_count, u64 display_id,
+ OutArray<DisplayMode, BufferAttr_HipcMapAlias> out_display_modes) {
+ LOG_WARNING(Service_VI, "(STUBBED) called, display_id={}", display_id);
+
+ if (!out_display_modes.empty()) {
+ out_display_modes[0] = {
+ .width = 1920,
+ .height = 1080,
+ .refresh_rate = 60.f,
+ .unknown = {},
+ };
+ *out_count = 1;
+ } else {
+ *out_count = 0;
+ }
+
+ R_SUCCEED();
+}
+
+Result ISystemDisplayService::GetDisplayMode(Out<DisplayMode> out_display_mode, u64 display_id) {
+ LOG_WARNING(Service_VI, "(STUBBED) called, display_id={}", display_id);
if (Settings::IsDockedMode()) {
- *out_width = static_cast<u32>(DisplayResolution::DockedWidth);
- *out_height = static_cast<u32>(DisplayResolution::DockedHeight);
+ out_display_mode->width = static_cast<u32>(DisplayResolution::DockedWidth);
+ out_display_mode->height = static_cast<u32>(DisplayResolution::DockedHeight);
} else {
- *out_width = static_cast<u32>(DisplayResolution::UndockedWidth);
- *out_height = static_cast<u32>(DisplayResolution::UndockedHeight);
+ out_display_mode->width = static_cast<u32>(DisplayResolution::UndockedWidth);
+ out_display_mode->height = static_cast<u32>(DisplayResolution::UndockedHeight);
}
- *out_refresh_rate = 60.f; // This wouldn't seem to be correct for 30 fps games.
- *out_unknown = 0;
+ out_display_mode->refresh_rate = 60.f; // This wouldn't seem to be correct for 30 fps games.
+ out_display_mode->unknown = 0;
R_SUCCEED();
}
Result ISystemDisplayService::GetSharedBufferMemoryHandleId(
Out<s32> out_nvmap_handle, Out<u64> out_size,
- OutLargeData<Nvnflinger::SharedMemoryPoolLayout, BufferAttr_HipcMapAlias> out_pool_layout,
- u64 buffer_id, ClientAppletResourceUserId aruid) {
+ OutLargeData<SharedMemoryPoolLayout, BufferAttr_HipcMapAlias> out_pool_layout, u64 buffer_id,
+ ClientAppletResourceUserId aruid) {
LOG_INFO(Service_VI, "called. buffer_id={}, aruid={:#x}", buffer_id, aruid.pid);
- R_RETURN(m_nvnflinger.GetSystemBufferManager().GetSharedBufferMemoryHandleId(
+ R_RETURN(m_container->GetSharedBufferManager()->GetSharedBufferMemoryHandleId(
out_size, out_nvmap_handle, out_pool_layout, buffer_id, aruid.pid));
}
@@ -122,7 +141,7 @@ Result ISystemDisplayService::AcquireSharedFrameBuffer(Out<android::Fence> out_f
Out<std::array<s32, 4>> out_slots,
Out<s64> out_target_slot, u64 layer_id) {
LOG_DEBUG(Service_VI, "called");
- R_RETURN(m_nvnflinger.GetSystemBufferManager().AcquireSharedFrameBuffer(
+ R_RETURN(m_container->GetSharedBufferManager()->AcquireSharedFrameBuffer(
out_fence, *out_slots, out_target_slot, layer_id));
}
@@ -131,15 +150,20 @@ Result ISystemDisplayService::PresentSharedFrameBuffer(android::Fence fence,
u32 window_transform, s32 swap_interval,
u64 layer_id, s64 surface_id) {
LOG_DEBUG(Service_VI, "called");
- R_RETURN(m_nvnflinger.GetSystemBufferManager().PresentSharedFrameBuffer(
+ R_RETURN(m_container->GetSharedBufferManager()->PresentSharedFrameBuffer(
fence, crop_region, window_transform, swap_interval, layer_id, surface_id));
}
Result ISystemDisplayService::GetSharedFrameBufferAcquirableEvent(
OutCopyHandle<Kernel::KReadableEvent> out_event, u64 layer_id) {
LOG_DEBUG(Service_VI, "called");
- R_RETURN(m_nvnflinger.GetSystemBufferManager().GetSharedFrameBufferAcquirableEvent(out_event,
- layer_id));
+ R_RETURN(m_container->GetSharedBufferManager()->GetSharedFrameBufferAcquirableEvent(out_event,
+ layer_id));
+}
+
+Result ISystemDisplayService::CancelSharedFrameBuffer(u64 layer_id, s64 slot) {
+ LOG_DEBUG(Service_VI, "called");
+ R_RETURN(m_container->GetSharedBufferManager()->CancelSharedFrameBuffer(layer_id, slot));
}
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/system_display_service.h b/src/core/hle/service/vi/system_display_service.h
index cfcb196fd..7228d826e 100644
--- a/src/core/hle/service/vi/system_display_service.h
+++ b/src/core/hle/service/vi/system_display_service.h
@@ -5,27 +5,28 @@
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/nvnflinger/ui/fence.h"
#include "core/hle/service/service.h"
-
-namespace Service::Nvnflinger {
-struct SharedMemoryPoolLayout;
-}
+#include "core/hle/service/vi/shared_buffer_manager.h"
namespace Service::VI {
+struct DisplayMode;
+
+class Container;
class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> {
public:
- explicit ISystemDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger);
+ explicit ISystemDisplayService(Core::System& system_, std::shared_ptr<Container> container);
~ISystemDisplayService() override;
private:
Result SetLayerZ(u32 z_value, u64 layer_id);
Result SetLayerVisibility(bool visible, u64 layer_id);
- Result GetDisplayMode(Out<u32> out_width, Out<u32> out_height, Out<f32> out_refresh_rate,
- Out<u32> out_unknown);
+ Result ListDisplayModes(Out<u64> out_count, u64 display_id,
+ OutArray<DisplayMode, BufferAttr_HipcMapAlias> out_display_modes);
+ Result GetDisplayMode(Out<DisplayMode> out_display_mode, u64 display_id);
Result GetSharedBufferMemoryHandleId(
Out<s32> out_nvmap_handle, Out<u64> out_size,
- OutLargeData<Nvnflinger::SharedMemoryPoolLayout, BufferAttr_HipcMapAlias> out_pool_layout,
+ OutLargeData<SharedMemoryPoolLayout, BufferAttr_HipcMapAlias> out_pool_layout,
u64 buffer_id, ClientAppletResourceUserId aruid);
Result OpenSharedLayer(u64 layer_id);
Result ConnectSharedLayer(u64 layer_id);
@@ -37,9 +38,10 @@ private:
Result PresentSharedFrameBuffer(android::Fence fence, Common::Rectangle<s32> crop_region,
u32 window_transform, s32 swap_interval, u64 layer_id,
s64 surface_id);
+ Result CancelSharedFrameBuffer(u64 layer_id, s64 slot);
private:
- Nvnflinger::Nvnflinger& m_nvnflinger;
+ const std::shared_ptr<Container> m_container;
};
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/system_root_service.cpp b/src/core/hle/service/vi/system_root_service.cpp
index 8789b4cfb..3489727d8 100644
--- a/src/core/hle/service/vi/system_root_service.cpp
+++ b/src/core/hle/service/vi/system_root_service.cpp
@@ -3,6 +3,7 @@
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/vi/application_display_service.h"
+#include "core/hle/service/vi/container.h"
#include "core/hle/service/vi/service_creator.h"
#include "core/hle/service/vi/system_root_service.h"
#include "core/hle/service/vi/vi.h"
@@ -10,10 +11,8 @@
namespace Service::VI {
-ISystemRootService::ISystemRootService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger,
- Nvnflinger::HosBinderDriverServer& hos_binder_driver_server)
- : ServiceFramework{system_, "vi:s"}, m_nvnflinger{nvnflinger}, m_hos_binder_driver_server{
- hos_binder_driver_server} {
+ISystemRootService::ISystemRootService(Core::System& system_, std::shared_ptr<Container> container)
+ : ServiceFramework{system_, "vi:s"}, m_container{std::move(container)} {
static const FunctionInfo functions[] = {
{1, C<&ISystemRootService::GetDisplayService>, "GetDisplayService"},
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
@@ -26,8 +25,8 @@ ISystemRootService::~ISystemRootService() = default;
Result ISystemRootService::GetDisplayService(
Out<SharedPointer<IApplicationDisplayService>> out_application_display_service, Policy policy) {
LOG_DEBUG(Service_VI, "called");
- R_RETURN(GetApplicationDisplayService(out_application_display_service, system, m_nvnflinger,
- m_hos_binder_driver_server, Permission::System, policy));
+ R_RETURN(GetApplicationDisplayService(out_application_display_service, system, m_container,
+ Permission::System, policy));
}
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/system_root_service.h b/src/core/hle/service/vi/system_root_service.h
index 2c547faa5..9d5aa53d3 100644
--- a/src/core/hle/service/vi/system_root_service.h
+++ b/src/core/hle/service/vi/system_root_service.h
@@ -10,20 +10,15 @@ namespace Core {
class System;
}
-namespace Service::Nvnflinger {
-class HosBinderDriverServer;
-class Nvnflinger;
-} // namespace Service::Nvnflinger
-
namespace Service::VI {
+class Container;
class IApplicationDisplayService;
enum class Policy : u32;
class ISystemRootService final : public ServiceFramework<ISystemRootService> {
public:
- explicit ISystemRootService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger,
- Nvnflinger::HosBinderDriverServer& hos_binder_driver_server);
+ explicit ISystemRootService(Core::System& system_, std::shared_ptr<Container> container);
~ISystemRootService() override;
private:
@@ -31,8 +26,7 @@ private:
Out<SharedPointer<IApplicationDisplayService>> out_application_display_service,
Policy policy);
- Nvnflinger::Nvnflinger& m_nvnflinger;
- Nvnflinger::HosBinderDriverServer& m_hos_binder_driver_server;
+ const std::shared_ptr<Container> m_container;
};
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 304e589b7..b388efaf6 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -1,25 +1,30 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "core/core.h"
#include "core/hle/service/server_manager.h"
-#include "core/hle/service/vi/application_display_service.h"
#include "core/hle/service/vi/application_root_service.h"
+#include "core/hle/service/vi/container.h"
#include "core/hle/service/vi/manager_root_service.h"
#include "core/hle/service/vi/system_root_service.h"
#include "core/hle/service/vi/vi.h"
namespace Service::VI {
-void LoopProcess(Core::System& system, Nvnflinger::Nvnflinger& nvnflinger,
- Nvnflinger::HosBinderDriverServer& hos_binder_driver_server) {
+void LoopProcess(Core::System& system, std::stop_token token) {
+ const auto container = std::make_shared<Container>(system);
+
auto server_manager = std::make_unique<ServerManager>(system);
- server_manager->RegisterNamedService("vi:m", std::make_shared<IManagerRootService>(
- system, nvnflinger, hos_binder_driver_server));
+ server_manager->RegisterNamedService("vi:m",
+ std::make_shared<IManagerRootService>(system, container));
+ server_manager->RegisterNamedService("vi:s",
+ std::make_shared<ISystemRootService>(system, container));
server_manager->RegisterNamedService(
- "vi:s", std::make_shared<ISystemRootService>(system, nvnflinger, hos_binder_driver_server));
- server_manager->RegisterNamedService("vi:u", std::make_shared<IApplicationRootService>(
- system, nvnflinger, hos_binder_driver_server));
+ "vi:u", std::make_shared<IApplicationRootService>(system, container));
+
+ std::stop_callback cb(token, [=] { container->OnTerminate(); });
+
ServerManager::RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h
index 8e681370d..7c1f350d8 100644
--- a/src/core/hle/service/vi/vi.h
+++ b/src/core/hle/service/vi/vi.h
@@ -3,18 +3,14 @@
#pragma once
+#include "common/polyfill_thread.h"
+
namespace Core {
class System;
}
-namespace Service::Nvnflinger {
-class HosBinderDriverServer;
-class Nvnflinger;
-} // namespace Service::Nvnflinger
-
namespace Service::VI {
-void LoopProcess(Core::System& system, Nvnflinger::Nvnflinger& nvnflinger,
- Nvnflinger::HosBinderDriverServer& hos_binder_driver_server);
+void LoopProcess(Core::System& system, std::stop_token token);
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_types.h b/src/core/hle/service/vi/vi_types.h
index 91e4b380c..95ff66358 100644
--- a/src/core/hle/service/vi/vi_types.h
+++ b/src/core/hle/service/vi/vi_types.h
@@ -66,9 +66,17 @@ struct DisplayInfo {
};
static_assert(sizeof(DisplayInfo) == 0x60, "DisplayInfo has wrong size");
+struct DisplayMode {
+ u32 width;
+ u32 height;
+ f32 refresh_rate;
+ u32 unknown;
+};
+static_assert(sizeof(DisplayMode) == 0x10, "DisplayMode has wrong size");
+
class NativeWindow final {
public:
- constexpr explicit NativeWindow(u32 id_) : id{id_} {}
+ constexpr explicit NativeWindow(s32 id_) : id{static_cast<u64>(id_)} {}
constexpr explicit NativeWindow(const NativeWindow& other) = default;
private:
diff --git a/src/core/hle/service/vi/vsync_manager.cpp b/src/core/hle/service/vi/vsync_manager.cpp
new file mode 100644
index 000000000..bdc4dfa96
--- /dev/null
+++ b/src/core/hle/service/vi/vsync_manager.cpp
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/os/event.h"
+#include "core/hle/service/vi/vsync_manager.h"
+
+namespace Service::VI {
+
+VsyncManager::VsyncManager() = default;
+VsyncManager::~VsyncManager() = default;
+
+void VsyncManager::SignalVsync() {
+ for (auto* event : m_vsync_events) {
+ event->Signal();
+ }
+}
+
+void VsyncManager::LinkVsyncEvent(Event* event) {
+ m_vsync_events.insert(event);
+}
+
+void VsyncManager::UnlinkVsyncEvent(Event* event) {
+ m_vsync_events.erase(event);
+}
+
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vsync_manager.h b/src/core/hle/service/vi/vsync_manager.h
new file mode 100644
index 000000000..5d45bb5ee
--- /dev/null
+++ b/src/core/hle/service/vi/vsync_manager.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <set>
+
+namespace Service {
+class Event;
+}
+
+namespace Service::VI {
+
+class DisplayList;
+
+class VsyncManager {
+public:
+ explicit VsyncManager();
+ ~VsyncManager();
+
+ void SignalVsync();
+ void LinkVsyncEvent(Event* event);
+ void UnlinkVsyncEvent(Event* event);
+
+private:
+ std::set<Event*> m_vsync_events;
+};
+
+} // namespace Service::VI
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index 2a32b1276..de27ec49e 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -118,7 +118,9 @@ ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)>
mbedtls_sha256_starts_ret(&ctx, 0);
// Ensure we maintain a clean state on exit.
- SCOPE_EXIT({ mbedtls_sha256_free(&ctx); });
+ SCOPE_EXIT {
+ mbedtls_sha256_free(&ctx);
+ };
// Declare counters.
const size_t total_size = file->GetSize();
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index e10a4601e..8775369a4 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -831,11 +831,11 @@ struct Memory::Impl {
if (core == sys_core) [[unlikely]] {
sys_core_guard.lock();
}
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (core == sys_core) [[unlikely]] {
sys_core_guard.unlock();
}
- });
+ };
gpu_device_memory->ApplyOpOnPointer(p, scratch_buffers[core], [&](DAddr address) {
auto& current_area = rasterizer_write_areas[core];
PAddr subaddress = address >> YUZU_PAGEBITS;
@@ -866,11 +866,11 @@ struct Memory::Impl {
if (core == sys_core) [[unlikely]] {
sys_core_guard.lock();
}
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (core == sys_core) [[unlikely]] {
sys_core_guard.unlock();
}
- });
+ };
auto& gpu = system.GPU();
gpu_device_memory->ApplyOpOnPointer(
p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size); });
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index b84b57d92..d8921e565 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -117,9 +117,9 @@ bool StandardVmCallbacks::IsAddressInRange(VAddr in) const {
(in < metadata.heap_extents.base ||
in >= metadata.heap_extents.base + metadata.heap_extents.size) &&
(in < metadata.alias_extents.base ||
- in >= metadata.heap_extents.base + metadata.alias_extents.size) &&
+ in >= metadata.alias_extents.base + metadata.alias_extents.size) &&
(in < metadata.aslr_extents.base ||
- in >= metadata.heap_extents.base + metadata.aslr_extents.size)) {
+ in >= metadata.aslr_extents.base + metadata.aslr_extents.size)) {
LOG_DEBUG(CheatEngine,
"Cheat attempting to access memory at invalid address={:016X}, if this "
"persists, "
diff --git a/src/core/memory/dmnt_cheat_vm.cpp b/src/core/memory/dmnt_cheat_vm.cpp
index f7097d01d..caceeec4f 100644
--- a/src/core/memory/dmnt_cheat_vm.cpp
+++ b/src/core/memory/dmnt_cheat_vm.cpp
@@ -224,12 +224,12 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
// If we've ever seen a decode failure, return false.
bool valid = decode_success;
CheatVmOpcode opcode = {};
- SCOPE_EXIT({
+ SCOPE_EXIT {
decode_success &= valid;
if (valid) {
out = opcode;
}
- });
+ };
// Helper function for getting instruction dwords.
const auto GetNextDword = [&] {
diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp
index 2bebfeef9..95f8c8c36 100644
--- a/src/frontend_common/config.cpp
+++ b/src/frontend_common/config.cpp
@@ -138,6 +138,7 @@ void Config::ReadPlayerValues(const std::size_t player_index) {
if (profile_name.empty()) {
// Use the global input config
player = Settings::values.players.GetValue(true)[player_index];
+ player.profile_name = "";
return;
}
player.profile_name = profile_name;
diff --git a/src/frontend_common/content_manager.h b/src/frontend_common/content_manager.h
index f3efe3465..c4e97a47b 100644
--- a/src/frontend_common/content_manager.h
+++ b/src/frontend_common/content_manager.h
@@ -251,11 +251,12 @@ inline InstallResult InstallNCA(FileSys::VfsFilesystem& vfs, const std::string&
* \param callback Callback to report the progress of the installation. The first size_t
* parameter is the total size of the installed contents and the second is the current progress. If
* you return true to the callback, it will cancel the installation as soon as possible.
+ * \param firmware_only Set to true to only scan system nand NCAs (firmware), post firmware install.
* \return A list of entries that failed to install. Returns an empty vector if successful.
*/
inline std::vector<std::string> VerifyInstalledContents(
Core::System& system, FileSys::ManualContentProvider& provider,
- const std::function<bool(size_t, size_t)>& callback) {
+ const std::function<bool(size_t, size_t)>& callback, bool firmware_only = false) {
// Get content registries.
auto bis_contents = system.GetFileSystemController().GetSystemNANDContents();
auto user_contents = system.GetFileSystemController().GetUserNANDContents();
@@ -264,7 +265,7 @@ inline std::vector<std::string> VerifyInstalledContents(
if (bis_contents) {
content_providers.push_back(bis_contents);
}
- if (user_contents) {
+ if (user_contents && !firmware_only) {
content_providers.push_back(user_contents);
}
diff --git a/src/hid_core/frontend/emulated_controller.cpp b/src/hid_core/frontend/emulated_controller.cpp
index 819460eb5..5cd26819c 100644
--- a/src/hid_core/frontend/emulated_controller.cpp
+++ b/src/hid_core/frontend/emulated_controller.cpp
@@ -174,18 +174,25 @@ void EmulatedController::LoadDevices() {
// Only map virtual devices to the first controller
if (npad_id_type == NpadIdType::Player1 || npad_id_type == NpadIdType::Handheld) {
camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
- ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
+#ifdef HAVE_LIBUSB
+ ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
+#endif
+#ifdef ANDROID
+ android_params = Common::ParamPackage{"engine:android,port:100"};
+#endif
}
output_params[LeftIndex] = left_joycon;
output_params[RightIndex] = right_joycon;
output_params[2] = camera_params[1];
output_params[3] = nfc_params[0];
+ output_params[4] = android_params;
output_params[LeftIndex].Set("output", true);
output_params[RightIndex].Set("output", true);
output_params[2].Set("output", true);
output_params[3].Set("output", true);
+ output_params[4].Set("output", true);
LoadTASParams();
LoadVirtualGamepadParams();
@@ -578,6 +585,9 @@ void EmulatedController::DisableConfiguration() {
// Get Joycon colors before turning on the controller
for (const auto& color_device : color_devices) {
+ if (color_device == nullptr) {
+ continue;
+ }
color_device->ForceUpdate();
}
@@ -923,8 +933,9 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
if (index >= controller.stick_values.size()) {
return;
}
- auto trigger_guard =
- SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Stick, !is_configuring); });
+ auto trigger_guard = SCOPE_GUARD {
+ TriggerOnChange(ControllerTriggerType::Stick, !is_configuring);
+ };
std::scoped_lock lock{mutex};
const auto stick_value = TransformToStick(callback);
@@ -979,8 +990,9 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
if (index >= controller.trigger_values.size()) {
return;
}
- auto trigger_guard =
- SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Trigger, !is_configuring); });
+ auto trigger_guard = SCOPE_GUARD {
+ TriggerOnChange(ControllerTriggerType::Trigger, !is_configuring);
+ };
std::scoped_lock lock{mutex};
const auto trigger_value = TransformToTrigger(callback);
@@ -1026,7 +1038,9 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
if (index >= controller.motion_values.size()) {
return;
}
- SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Motion, !is_configuring); });
+ SCOPE_EXIT {
+ TriggerOnChange(ControllerTriggerType::Motion, !is_configuring);
+ };
std::scoped_lock lock{mutex};
auto& raw_status = controller.motion_values[index].raw_status;
auto& emulated = controller.motion_values[index].emulated;
@@ -1060,8 +1074,9 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback
if (index >= controller.color_values.size()) {
return;
}
- auto trigger_guard =
- SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Color, !is_configuring); });
+ auto trigger_guard = SCOPE_GUARD {
+ TriggerOnChange(ControllerTriggerType::Color, !is_configuring);
+ };
std::scoped_lock lock{mutex};
controller.color_values[index] = TransformToColor(callback);
@@ -1110,7 +1125,9 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
if (index >= controller.battery_values.size()) {
return;
}
- SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Battery, !is_configuring); });
+ SCOPE_EXIT {
+ TriggerOnChange(ControllerTriggerType::Battery, !is_configuring);
+ };
std::scoped_lock lock{mutex};
controller.battery_values[index] = TransformToBattery(callback);
@@ -1173,7 +1190,9 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
}
void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback) {
- SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::IrSensor, !is_configuring); });
+ SCOPE_EXIT {
+ TriggerOnChange(ControllerTriggerType::IrSensor, !is_configuring);
+ };
std::scoped_lock lock{mutex};
controller.camera_values = TransformToCamera(callback);
@@ -1188,7 +1207,9 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback
}
void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
- SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::RingController, !is_configuring); });
+ SCOPE_EXIT {
+ TriggerOnChange(ControllerTriggerType::RingController, !is_configuring);
+ };
std::scoped_lock lock{mutex};
const auto force_value = TransformToStick(callback);
@@ -1202,7 +1223,9 @@ void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& call
}
void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
- SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Nfc, !is_configuring); });
+ SCOPE_EXIT {
+ TriggerOnChange(ControllerTriggerType::Nfc, !is_configuring);
+ };
std::scoped_lock lock{mutex};
controller.nfc_values = TransformToNfc(callback);
@@ -1277,6 +1300,10 @@ bool EmulatedController::SetVibration(DeviceIndex device_index, const VibrationV
.high_frequency = vibration.high_frequency,
.type = type,
};
+
+ // Send vibrations to Android's input overlay
+ output_devices[4]->SetVibration(status);
+
return output_devices[index]->SetVibration(status) == Common::Input::DriverResult::Success;
}
@@ -1671,8 +1698,9 @@ void EmulatedController::Connect(bool use_temporary_value) {
return;
}
- auto trigger_guard =
- SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); });
+ auto trigger_guard = SCOPE_GUARD {
+ TriggerOnChange(ControllerTriggerType::Connected, !is_configuring);
+ };
std::scoped_lock lock{connect_mutex, mutex};
if (is_configuring) {
tmp_is_connected = true;
@@ -1687,8 +1715,9 @@ void EmulatedController::Connect(bool use_temporary_value) {
}
void EmulatedController::Disconnect() {
- auto trigger_guard =
- SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); });
+ auto trigger_guard = SCOPE_GUARD {
+ TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring);
+ };
std::scoped_lock lock{connect_mutex, mutex};
if (is_configuring) {
tmp_is_connected = false;
@@ -1724,8 +1753,9 @@ NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) c
}
void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
- auto trigger_guard =
- SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Type, !is_configuring); });
+ auto trigger_guard = SCOPE_GUARD {
+ TriggerOnChange(ControllerTriggerType::Type, !is_configuring);
+ };
std::scoped_lock lock{mutex, npad_mutex};
if (is_configuring) {
diff --git a/src/hid_core/frontend/emulated_controller.h b/src/hid_core/frontend/emulated_controller.h
index 701b38300..ab3c6fcd3 100644
--- a/src/hid_core/frontend/emulated_controller.h
+++ b/src/hid_core/frontend/emulated_controller.h
@@ -21,7 +21,7 @@
namespace Core::HID {
const std::size_t max_emulated_controllers = 2;
-const std::size_t output_devices_size = 4;
+const std::size_t output_devices_size = 5;
struct ControllerMotionInfo {
Common::Input::MotionStatus raw_status{};
MotionInput emulated{};
@@ -597,6 +597,7 @@ private:
CameraParams camera_params;
RingAnalogParams ring_params;
NfcParams nfc_params;
+ Common::ParamPackage android_params;
OutputParams output_params;
ButtonDevices button_devices;
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index d0a71a15b..d455323e0 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -2,8 +2,6 @@
# SPDX-License-Identifier: GPL-2.0-or-later
add_library(input_common STATIC
- drivers/android.cpp
- drivers/android.h
drivers/camera.cpp
drivers/camera.h
drivers/keyboard.cpp
@@ -94,3 +92,11 @@ target_link_libraries(input_common PUBLIC hid_core PRIVATE common Boost::headers
if (YUZU_USE_PRECOMPILED_HEADERS)
target_precompile_headers(input_common PRIVATE precompiled_headers.h)
endif()
+
+if (ANDROID)
+ target_sources(input_common PRIVATE
+ drivers/android.cpp
+ drivers/android.h
+ )
+ target_link_libraries(input_common PRIVATE android)
+endif()
diff --git a/src/input_common/drivers/android.cpp b/src/input_common/drivers/android.cpp
index b6a03fdc0..e859cc538 100644
--- a/src/input_common/drivers/android.cpp
+++ b/src/input_common/drivers/android.cpp
@@ -1,30 +1,47 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
+#include <set>
+#include <common/settings_input.h>
+#include <jni.h>
+#include "common/android/android_common.h"
+#include "common/android/id_cache.h"
#include "input_common/drivers/android.h"
namespace InputCommon {
Android::Android(std::string input_engine_) : InputEngine(std::move(input_engine_)) {}
-void Android::RegisterController(std::size_t controller_number) {
- PreSetController(GetIdentifier(controller_number));
+void Android::RegisterController(jobject j_input_device) {
+ auto env = Common::Android::GetEnvForThread();
+ const std::string guid = Common::Android::GetJString(
+ env, static_cast<jstring>(
+ env->CallObjectMethod(j_input_device, Common::Android::GetYuzuDeviceGetGUID())));
+ const s32 port = env->CallIntMethod(j_input_device, Common::Android::GetYuzuDeviceGetPort());
+ const auto identifier = GetIdentifier(guid, static_cast<size_t>(port));
+ PreSetController(identifier);
+
+ if (input_devices.find(identifier) != input_devices.end()) {
+ env->DeleteGlobalRef(input_devices[identifier]);
+ }
+ auto new_device = env->NewGlobalRef(j_input_device);
+ input_devices[identifier] = new_device;
}
-void Android::SetButtonState(std::size_t controller_number, int button_id, bool value) {
- const auto identifier = GetIdentifier(controller_number);
+void Android::SetButtonState(std::string guid, size_t port, int button_id, bool value) {
+ const auto identifier = GetIdentifier(guid, port);
SetButton(identifier, button_id, value);
}
-void Android::SetAxisState(std::size_t controller_number, int axis_id, float value) {
- const auto identifier = GetIdentifier(controller_number);
+void Android::SetAxisPosition(std::string guid, size_t port, int axis_id, float value) {
+ const auto identifier = GetIdentifier(guid, port);
SetAxis(identifier, axis_id, value);
}
-void Android::SetMotionState(std::size_t controller_number, u64 delta_timestamp, float gyro_x,
+void Android::SetMotionState(std::string guid, size_t port, u64 delta_timestamp, float gyro_x,
float gyro_y, float gyro_z, float accel_x, float accel_y,
float accel_z) {
- const auto identifier = GetIdentifier(controller_number);
+ const auto identifier = GetIdentifier(guid, port);
const BasicMotion motion_data{
.gyro_x = gyro_x,
.gyro_y = gyro_y,
@@ -37,10 +54,295 @@ void Android::SetMotionState(std::size_t controller_number, u64 delta_timestamp,
SetMotion(identifier, 0, motion_data);
}
-PadIdentifier Android::GetIdentifier(std::size_t controller_number) const {
+Common::Input::DriverResult Android::SetVibration(
+ [[maybe_unused]] const PadIdentifier& identifier,
+ [[maybe_unused]] const Common::Input::VibrationStatus& vibration) {
+ auto device = input_devices.find(identifier);
+ if (device != input_devices.end()) {
+ Common::Android::RunJNIOnFiber<void>([&](JNIEnv* env) {
+ float average_intensity =
+ static_cast<float>((vibration.high_amplitude + vibration.low_amplitude) / 2.0);
+ env->CallVoidMethod(device->second, Common::Android::GetYuzuDeviceVibrate(),
+ average_intensity);
+ });
+ return Common::Input::DriverResult::Success;
+ }
+ return Common::Input::DriverResult::NotSupported;
+}
+
+bool Android::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) {
+ auto device = input_devices.find(identifier);
+ if (device != input_devices.end()) {
+ return Common::Android::RunJNIOnFiber<bool>([&](JNIEnv* env) {
+ return static_cast<bool>(env->CallBooleanMethod(
+ device->second, Common::Android::GetYuzuDeviceGetSupportsVibration()));
+ });
+ }
+ return false;
+}
+
+std::vector<Common::ParamPackage> Android::GetInputDevices() const {
+ std::vector<Common::ParamPackage> devices;
+ auto env = Common::Android::GetEnvForThread();
+ for (const auto& [key, value] : input_devices) {
+ auto name_object = static_cast<jstring>(
+ env->CallObjectMethod(value, Common::Android::GetYuzuDeviceGetName()));
+ const std::string name =
+ fmt::format("{} {}", Common::Android::GetJString(env, name_object), key.port);
+ devices.emplace_back(Common::ParamPackage{
+ {"engine", GetEngineName()},
+ {"display", std::move(name)},
+ {"guid", key.guid.RawString()},
+ {"port", std::to_string(key.port)},
+ });
+ }
+ return devices;
+}
+
+std::set<s32> Android::GetDeviceAxes(JNIEnv* env, jobject& j_device) const {
+ auto j_axes = static_cast<jobjectArray>(
+ env->CallObjectMethod(j_device, Common::Android::GetYuzuDeviceGetAxes()));
+ std::set<s32> axes;
+ for (int i = 0; i < env->GetArrayLength(j_axes); ++i) {
+ jobject axis = env->GetObjectArrayElement(j_axes, i);
+ axes.insert(env->GetIntField(axis, Common::Android::GetIntegerValueField()));
+ }
+ return axes;
+}
+
+Common::ParamPackage Android::BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
+ int axis_y) const {
+ Common::ParamPackage params;
+ params.Set("engine", GetEngineName());
+ params.Set("port", static_cast<int>(identifier.port));
+ params.Set("guid", identifier.guid.RawString());
+ params.Set("axis_x", axis_x);
+ params.Set("axis_y", axis_y);
+ params.Set("offset_x", 0);
+ params.Set("offset_y", 0);
+ params.Set("invert_x", "+");
+
+ // Invert Y-Axis by default
+ params.Set("invert_y", "-");
+ return params;
+}
+
+Common::ParamPackage Android::BuildAnalogParamPackageForButton(PadIdentifier identifier, s32 axis,
+ bool invert) const {
+ Common::ParamPackage params{};
+ params.Set("engine", GetEngineName());
+ params.Set("port", static_cast<int>(identifier.port));
+ params.Set("guid", identifier.guid.RawString());
+ params.Set("axis", axis);
+ params.Set("threshold", "0.5");
+ params.Set("invert", invert ? "-" : "+");
+ return params;
+}
+
+Common::ParamPackage Android::BuildButtonParamPackageForButton(PadIdentifier identifier,
+ s32 button) const {
+ Common::ParamPackage params{};
+ params.Set("engine", GetEngineName());
+ params.Set("port", static_cast<int>(identifier.port));
+ params.Set("guid", identifier.guid.RawString());
+ params.Set("button", button);
+ return params;
+}
+
+bool Android::MatchVID(Common::UUID device, const std::vector<std::string>& vids) const {
+ for (size_t i = 0; i < vids.size(); ++i) {
+ auto fucker = device.RawString();
+ if (fucker.find(vids[i]) != std::string::npos) {
+ return true;
+ }
+ }
+ return false;
+}
+
+AnalogMapping Android::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
+ if (!params.Has("guid") || !params.Has("port")) {
+ return {};
+ }
+
+ auto identifier =
+ GetIdentifier(params.Get("guid", ""), static_cast<size_t>(params.Get("port", 0)));
+ auto& j_device = input_devices[identifier];
+ if (j_device == nullptr) {
+ return {};
+ }
+
+ auto env = Common::Android::GetEnvForThread();
+ std::set<s32> axes = GetDeviceAxes(env, j_device);
+ if (axes.size() == 0) {
+ return {};
+ }
+
+ AnalogMapping mapping = {};
+ if (axes.find(AXIS_X) != axes.end() && axes.find(AXIS_Y) != axes.end()) {
+ mapping.insert_or_assign(Settings::NativeAnalog::LStick,
+ BuildParamPackageForAnalog(identifier, AXIS_X, AXIS_Y));
+ }
+
+ if (axes.find(AXIS_RX) != axes.end() && axes.find(AXIS_RY) != axes.end()) {
+ mapping.insert_or_assign(Settings::NativeAnalog::RStick,
+ BuildParamPackageForAnalog(identifier, AXIS_RX, AXIS_RY));
+ } else if (axes.find(AXIS_Z) != axes.end() && axes.find(AXIS_RZ) != axes.end()) {
+ mapping.insert_or_assign(Settings::NativeAnalog::RStick,
+ BuildParamPackageForAnalog(identifier, AXIS_Z, AXIS_RZ));
+ }
+ return mapping;
+}
+
+ButtonMapping Android::GetButtonMappingForDevice(const Common::ParamPackage& params) {
+ if (!params.Has("guid") || !params.Has("port")) {
+ return {};
+ }
+
+ auto identifier =
+ GetIdentifier(params.Get("guid", ""), static_cast<size_t>(params.Get("port", 0)));
+ auto& j_device = input_devices[identifier];
+ if (j_device == nullptr) {
+ return {};
+ }
+
+ auto env = Common::Android::GetEnvForThread();
+ jintArray j_keys = env->NewIntArray(static_cast<int>(keycode_ids.size()));
+ env->SetIntArrayRegion(j_keys, 0, static_cast<int>(keycode_ids.size()), keycode_ids.data());
+ auto j_has_keys_object = static_cast<jbooleanArray>(
+ env->CallObjectMethod(j_device, Common::Android::GetYuzuDeviceHasKeys(), j_keys));
+ jboolean isCopy = false;
+ jboolean* j_has_keys = env->GetBooleanArrayElements(j_has_keys_object, &isCopy);
+
+ std::set<s32> available_keys;
+ for (size_t i = 0; i < keycode_ids.size(); ++i) {
+ if (j_has_keys[i]) {
+ available_keys.insert(keycode_ids[i]);
+ }
+ }
+
+ // Some devices use axes instead of buttons for certain controls so we need all the axes here
+ std::set<s32> axes = GetDeviceAxes(env, j_device);
+
+ ButtonMapping mapping = {};
+ if (axes.find(AXIS_HAT_X) != axes.end() && axes.find(AXIS_HAT_Y) != axes.end()) {
+ mapping.insert_or_assign(Settings::NativeButton::DUp,
+ BuildAnalogParamPackageForButton(identifier, AXIS_HAT_Y, true));
+ mapping.insert_or_assign(Settings::NativeButton::DDown,
+ BuildAnalogParamPackageForButton(identifier, AXIS_HAT_Y, false));
+ mapping.insert_or_assign(Settings::NativeButton::DLeft,
+ BuildAnalogParamPackageForButton(identifier, AXIS_HAT_X, true));
+ mapping.insert_or_assign(Settings::NativeButton::DRight,
+ BuildAnalogParamPackageForButton(identifier, AXIS_HAT_X, false));
+ } else if (available_keys.find(KEYCODE_DPAD_UP) != available_keys.end() &&
+ available_keys.find(KEYCODE_DPAD_DOWN) != available_keys.end() &&
+ available_keys.find(KEYCODE_DPAD_LEFT) != available_keys.end() &&
+ available_keys.find(KEYCODE_DPAD_RIGHT) != available_keys.end()) {
+ mapping.insert_or_assign(Settings::NativeButton::DUp,
+ BuildButtonParamPackageForButton(identifier, KEYCODE_DPAD_UP));
+ mapping.insert_or_assign(Settings::NativeButton::DDown,
+ BuildButtonParamPackageForButton(identifier, KEYCODE_DPAD_DOWN));
+ mapping.insert_or_assign(Settings::NativeButton::DLeft,
+ BuildButtonParamPackageForButton(identifier, KEYCODE_DPAD_LEFT));
+ mapping.insert_or_assign(Settings::NativeButton::DRight,
+ BuildButtonParamPackageForButton(identifier, KEYCODE_DPAD_RIGHT));
+ }
+
+ if (axes.find(AXIS_LTRIGGER) != axes.end()) {
+ mapping.insert_or_assign(Settings::NativeButton::ZL, BuildAnalogParamPackageForButton(
+ identifier, AXIS_LTRIGGER, false));
+ } else if (available_keys.find(KEYCODE_BUTTON_L2) != available_keys.end()) {
+ mapping.insert_or_assign(Settings::NativeButton::ZL,
+ BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_L2));
+ }
+
+ if (axes.find(AXIS_RTRIGGER) != axes.end()) {
+ mapping.insert_or_assign(Settings::NativeButton::ZR, BuildAnalogParamPackageForButton(
+ identifier, AXIS_RTRIGGER, false));
+ } else if (available_keys.find(KEYCODE_BUTTON_R2) != available_keys.end()) {
+ mapping.insert_or_assign(Settings::NativeButton::ZR,
+ BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_R2));
+ }
+
+ if (available_keys.find(KEYCODE_BUTTON_A) != available_keys.end()) {
+ if (MatchVID(identifier.guid, flipped_ab_vids)) {
+ mapping.insert_or_assign(Settings::NativeButton::B, BuildButtonParamPackageForButton(
+ identifier, KEYCODE_BUTTON_A));
+ } else {
+ mapping.insert_or_assign(Settings::NativeButton::A, BuildButtonParamPackageForButton(
+ identifier, KEYCODE_BUTTON_A));
+ }
+ }
+ if (available_keys.find(KEYCODE_BUTTON_B) != available_keys.end()) {
+ if (MatchVID(identifier.guid, flipped_ab_vids)) {
+ mapping.insert_or_assign(Settings::NativeButton::A, BuildButtonParamPackageForButton(
+ identifier, KEYCODE_BUTTON_B));
+ } else {
+ mapping.insert_or_assign(Settings::NativeButton::B, BuildButtonParamPackageForButton(
+ identifier, KEYCODE_BUTTON_B));
+ }
+ }
+ if (available_keys.find(KEYCODE_BUTTON_X) != available_keys.end()) {
+ if (MatchVID(identifier.guid, flipped_xy_vids)) {
+ mapping.insert_or_assign(Settings::NativeButton::Y, BuildButtonParamPackageForButton(
+ identifier, KEYCODE_BUTTON_X));
+ } else {
+ mapping.insert_or_assign(Settings::NativeButton::X, BuildButtonParamPackageForButton(
+ identifier, KEYCODE_BUTTON_X));
+ }
+ }
+ if (available_keys.find(KEYCODE_BUTTON_Y) != available_keys.end()) {
+ if (MatchVID(identifier.guid, flipped_xy_vids)) {
+ mapping.insert_or_assign(Settings::NativeButton::X, BuildButtonParamPackageForButton(
+ identifier, KEYCODE_BUTTON_Y));
+ } else {
+ mapping.insert_or_assign(Settings::NativeButton::Y, BuildButtonParamPackageForButton(
+ identifier, KEYCODE_BUTTON_Y));
+ }
+ }
+
+ if (available_keys.find(KEYCODE_BUTTON_L1) != available_keys.end()) {
+ mapping.insert_or_assign(Settings::NativeButton::L,
+ BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_L1));
+ }
+ if (available_keys.find(KEYCODE_BUTTON_R1) != available_keys.end()) {
+ mapping.insert_or_assign(Settings::NativeButton::R,
+ BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_R1));
+ }
+
+ if (available_keys.find(KEYCODE_BUTTON_THUMBL) != available_keys.end()) {
+ mapping.insert_or_assign(
+ Settings::NativeButton::LStick,
+ BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_THUMBL));
+ }
+ if (available_keys.find(KEYCODE_BUTTON_THUMBR) != available_keys.end()) {
+ mapping.insert_or_assign(
+ Settings::NativeButton::RStick,
+ BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_THUMBR));
+ }
+
+ if (available_keys.find(KEYCODE_BUTTON_START) != available_keys.end()) {
+ mapping.insert_or_assign(
+ Settings::NativeButton::Plus,
+ BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_START));
+ }
+ if (available_keys.find(KEYCODE_BUTTON_SELECT) != available_keys.end()) {
+ mapping.insert_or_assign(
+ Settings::NativeButton::Minus,
+ BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_SELECT));
+ }
+
+ return mapping;
+}
+
+Common::Input::ButtonNames Android::GetUIName(
+ [[maybe_unused]] const Common::ParamPackage& params) const {
+ return Common::Input::ButtonNames::Value;
+}
+
+PadIdentifier Android::GetIdentifier(const std::string& guid, size_t port) const {
return {
- .guid = Common::UUID{},
- .port = controller_number,
+ .guid = Common::UUID{guid},
+ .port = port,
.pad = 0,
};
}
diff --git a/src/input_common/drivers/android.h b/src/input_common/drivers/android.h
index 3f01817f6..8a386c1b1 100644
--- a/src/input_common/drivers/android.h
+++ b/src/input_common/drivers/android.h
@@ -3,6 +3,8 @@
#pragma once
+#include <set>
+#include <jni.h>
#include "input_common/input_engine.h"
namespace InputCommon {
@@ -15,40 +17,122 @@ public:
explicit Android(std::string input_engine_);
/**
- * Registers controller number to accept new inputs
- * @param controller_number the controller number that will take this action
+ * Registers controller number to accept new inputs.
+ * @param j_input_device YuzuInputDevice object from the Android frontend to register.
*/
- void RegisterController(std::size_t controller_number);
+ void RegisterController(jobject j_input_device);
/**
- * Sets the status of all buttons bound with the key to pressed
- * @param controller_number the controller number that will take this action
- * @param button_id the id of the button
- * @param value indicates if the button is pressed or not
+ * Sets the status of a button on a specific controller.
+ * @param guid 32 character hexadecimal string consisting of the controller's PID+VID.
+ * @param port Port determined by controller connection order.
+ * @param button_id The Android Keycode corresponding to this event.
+ * @param value Whether the button is pressed or not.
*/
- void SetButtonState(std::size_t controller_number, int button_id, bool value);
+ void SetButtonState(std::string guid, size_t port, int button_id, bool value);
/**
- * Sets the status of a analog input to a specific player index
- * @param controller_number the controller number that will take this action
- * @param axis_id the id of the axis to move
- * @param value the analog position of the axis
+ * Sets the status of an axis on a specific controller.
+ * @param guid 32 character hexadecimal string consisting of the controller's PID+VID.
+ * @param port Port determined by controller connection order.
+ * @param axis_id The Android axis ID corresponding to this event.
+ * @param value Value along the given axis.
*/
- void SetAxisState(std::size_t controller_number, int axis_id, float value);
+ void SetAxisPosition(std::string guid, size_t port, int axis_id, float value);
/**
- * Sets the status of the motion sensor to a specific player index
- * @param controller_number the controller number that will take this action
- * @param delta_timestamp time passed since last reading
- * @param gyro_x,gyro_y,gyro_z the gyro sensor readings
- * @param accel_x,accel_y,accel_z the accelerometer reading
+ * Sets the status of the motion sensor on a specific controller
+ * @param guid 32 character hexadecimal string consisting of the controller's PID+VID.
+ * @param port Port determined by controller connection order.
+ * @param delta_timestamp Time passed since the last read.
+ * @param gyro_x,gyro_y,gyro_z Gyro sensor readings.
+ * @param accel_x,accel_y,accel_z Accelerometer sensor readings.
*/
- void SetMotionState(std::size_t controller_number, u64 delta_timestamp, float gyro_x,
+ void SetMotionState(std::string guid, size_t port, u64 delta_timestamp, float gyro_x,
float gyro_y, float gyro_z, float accel_x, float accel_y, float accel_z);
+ Common::Input::DriverResult SetVibration(
+ const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
+
+ bool IsVibrationEnabled(const PadIdentifier& identifier) override;
+
+ std::vector<Common::ParamPackage> GetInputDevices() const override;
+
+ /**
+ * Gets the axes reported by the YuzuInputDevice.
+ * @param env JNI environment pointer.
+ * @param j_device YuzuInputDevice from the Android frontend.
+ * @return Set of the axes reported by the underlying Android InputDevice
+ */
+ std::set<s32> GetDeviceAxes(JNIEnv* env, jobject& j_device) const;
+
+ Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
+ int axis_y) const;
+
+ Common::ParamPackage BuildAnalogParamPackageForButton(PadIdentifier identifier, s32 axis,
+ bool invert) const;
+
+ Common::ParamPackage BuildButtonParamPackageForButton(PadIdentifier identifier,
+ s32 button) const;
+
+ bool MatchVID(Common::UUID device, const std::vector<std::string>& vids) const;
+
+ AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
+
+ ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
+
+ Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
+
private:
+ std::unordered_map<PadIdentifier, jobject> input_devices;
+
/// Returns the correct identifier corresponding to the player index
- PadIdentifier GetIdentifier(std::size_t controller_number) const;
+ PadIdentifier GetIdentifier(const std::string& guid, size_t port) const;
+
+ static constexpr s32 AXIS_X = 0;
+ static constexpr s32 AXIS_Y = 1;
+ static constexpr s32 AXIS_Z = 11;
+ static constexpr s32 AXIS_RX = 12;
+ static constexpr s32 AXIS_RY = 13;
+ static constexpr s32 AXIS_RZ = 14;
+ static constexpr s32 AXIS_HAT_X = 15;
+ static constexpr s32 AXIS_HAT_Y = 16;
+ static constexpr s32 AXIS_LTRIGGER = 17;
+ static constexpr s32 AXIS_RTRIGGER = 18;
+
+ static constexpr s32 KEYCODE_DPAD_UP = 19;
+ static constexpr s32 KEYCODE_DPAD_DOWN = 20;
+ static constexpr s32 KEYCODE_DPAD_LEFT = 21;
+ static constexpr s32 KEYCODE_DPAD_RIGHT = 22;
+ static constexpr s32 KEYCODE_BUTTON_A = 96;
+ static constexpr s32 KEYCODE_BUTTON_B = 97;
+ static constexpr s32 KEYCODE_BUTTON_X = 99;
+ static constexpr s32 KEYCODE_BUTTON_Y = 100;
+ static constexpr s32 KEYCODE_BUTTON_L1 = 102;
+ static constexpr s32 KEYCODE_BUTTON_R1 = 103;
+ static constexpr s32 KEYCODE_BUTTON_L2 = 104;
+ static constexpr s32 KEYCODE_BUTTON_R2 = 105;
+ static constexpr s32 KEYCODE_BUTTON_THUMBL = 106;
+ static constexpr s32 KEYCODE_BUTTON_THUMBR = 107;
+ static constexpr s32 KEYCODE_BUTTON_START = 108;
+ static constexpr s32 KEYCODE_BUTTON_SELECT = 109;
+ const std::vector<s32> keycode_ids{
+ KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_RIGHT,
+ KEYCODE_BUTTON_A, KEYCODE_BUTTON_B, KEYCODE_BUTTON_X, KEYCODE_BUTTON_Y,
+ KEYCODE_BUTTON_L1, KEYCODE_BUTTON_R1, KEYCODE_BUTTON_L2, KEYCODE_BUTTON_R2,
+ KEYCODE_BUTTON_THUMBL, KEYCODE_BUTTON_THUMBR, KEYCODE_BUTTON_START, KEYCODE_BUTTON_SELECT,
+ };
+
+ const std::string sony_vid{"054c"};
+ const std::string nintendo_vid{"057e"};
+ const std::string razer_vid{"1532"};
+ const std::string redmagic_vid{"3537"};
+ const std::string backbone_labs_vid{"358a"};
+ const std::string xbox_vid{"045e"};
+ const std::vector<std::string> flipped_ab_vids{sony_vid, nintendo_vid, razer_vid,
+ redmagic_vid, backbone_labs_vid, xbox_vid};
+ const std::vector<std::string> flipped_xy_vids{sony_vid, razer_vid, redmagic_vid,
+ backbone_labs_vid, xbox_vid};
};
} // namespace InputCommon
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index c9f903213..0dd1c958a 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -268,7 +268,9 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
}
Common::Input::DriverResult JoyconDriver::SetPollingMode() {
- SCOPE_EXIT({ disable_input_thread = false; });
+ SCOPE_EXIT {
+ disable_input_thread = false;
+ };
disable_input_thread = true;
rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration);
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index f8749ebbf..62a7ae40f 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -4,7 +4,6 @@
#include <memory>
#include "common/input.h"
#include "common/param_package.h"
-#include "input_common/drivers/android.h"
#include "input_common/drivers/camera.h"
#include "input_common/drivers/keyboard.h"
#include "input_common/drivers/mouse.h"
@@ -28,6 +27,10 @@
#include "input_common/drivers/sdl_driver.h"
#endif
+#ifdef ANDROID
+#include "input_common/drivers/android.h"
+#endif
+
namespace InputCommon {
/// Dummy engine to get periodic updates
@@ -79,7 +82,9 @@ struct InputSubsystem::Impl {
RegisterEngine("cemuhookudp", udp_client);
RegisterEngine("tas", tas_input);
RegisterEngine("camera", camera);
+#ifdef ANDROID
RegisterEngine("android", android);
+#endif
RegisterEngine("virtual_amiibo", virtual_amiibo);
RegisterEngine("virtual_gamepad", virtual_gamepad);
#ifdef HAVE_SDL2
@@ -111,7 +116,9 @@ struct InputSubsystem::Impl {
UnregisterEngine(udp_client);
UnregisterEngine(tas_input);
UnregisterEngine(camera);
+#ifdef ANDROID
UnregisterEngine(android);
+#endif
UnregisterEngine(virtual_amiibo);
UnregisterEngine(virtual_gamepad);
#ifdef HAVE_SDL2
@@ -128,12 +135,16 @@ struct InputSubsystem::Impl {
Common::ParamPackage{{"display", "Any"}, {"engine", "any"}},
};
+#ifndef ANDROID
auto keyboard_devices = keyboard->GetInputDevices();
devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end());
auto mouse_devices = mouse->GetInputDevices();
devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end());
+#endif
+#ifdef ANDROID
auto android_devices = android->GetInputDevices();
devices.insert(devices.end(), android_devices.begin(), android_devices.end());
+#endif
#ifdef HAVE_LIBUSB
auto gcadapter_devices = gcadapter->GetInputDevices();
devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
@@ -162,9 +173,11 @@ struct InputSubsystem::Impl {
if (engine == mouse->GetEngineName()) {
return mouse;
}
+#ifdef ANDROID
if (engine == android->GetEngineName()) {
return android;
}
+#endif
#ifdef HAVE_LIBUSB
if (engine == gcadapter->GetEngineName()) {
return gcadapter;
@@ -245,9 +258,11 @@ struct InputSubsystem::Impl {
if (engine == mouse->GetEngineName()) {
return true;
}
+#ifdef ANDROID
if (engine == android->GetEngineName()) {
return true;
}
+#endif
#ifdef HAVE_LIBUSB
if (engine == gcadapter->GetEngineName()) {
return true;
@@ -276,7 +291,9 @@ struct InputSubsystem::Impl {
void BeginConfiguration() {
keyboard->BeginConfiguration();
mouse->BeginConfiguration();
+#ifdef ANDROID
android->BeginConfiguration();
+#endif
#ifdef HAVE_LIBUSB
gcadapter->BeginConfiguration();
#endif
@@ -290,7 +307,9 @@ struct InputSubsystem::Impl {
void EndConfiguration() {
keyboard->EndConfiguration();
mouse->EndConfiguration();
+#ifdef ANDROID
android->EndConfiguration();
+#endif
#ifdef HAVE_LIBUSB
gcadapter->EndConfiguration();
#endif
@@ -321,7 +340,6 @@ struct InputSubsystem::Impl {
std::shared_ptr<TasInput::Tas> tas_input;
std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
std::shared_ptr<Camera> camera;
- std::shared_ptr<Android> android;
std::shared_ptr<VirtualAmiibo> virtual_amiibo;
std::shared_ptr<VirtualGamepad> virtual_gamepad;
@@ -333,6 +351,10 @@ struct InputSubsystem::Impl {
std::shared_ptr<SDLDriver> sdl;
std::shared_ptr<Joycons> joycon;
#endif
+
+#ifdef ANDROID
+ std::shared_ptr<Android> android;
+#endif
};
InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
@@ -387,6 +409,7 @@ const Camera* InputSubsystem::GetCamera() const {
return impl->camera.get();
}
+#ifdef ANDROID
Android* InputSubsystem::GetAndroid() {
return impl->android.get();
}
@@ -394,6 +417,7 @@ Android* InputSubsystem::GetAndroid() {
const Android* InputSubsystem::GetAndroid() const {
return impl->android.get();
}
+#endif
VirtualAmiibo* InputSubsystem::GetVirtualAmiibo() {
return impl->virtual_amiibo.get();
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
index 44281e407..945cdb42b 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
@@ -60,11 +60,10 @@ public:
Add(spv::ImageOperandsMask::ConstOffsets, offsets);
}
- explicit ImageOperands(EmitContext& ctx, const IR::Value& offset, Id lod, Id ms) {
+ explicit ImageOperands(Id lod, Id ms) {
if (Sirit::ValidId(lod)) {
Add(spv::ImageOperandsMask::Lod, lod);
}
- AddOffset(ctx, offset, ImageFetchOffsetAllowed);
if (Sirit::ValidId(ms)) {
Add(spv::ImageOperandsMask::Sample, ms);
}
@@ -312,6 +311,43 @@ Id ImageGatherSubpixelOffset(EmitContext& ctx, const IR::TextureInstInfo& info,
return coords;
}
}
+
+void AddOffsetToCoordinates(EmitContext& ctx, const IR::TextureInstInfo& info, Id& coords,
+ Id offset) {
+ if (!Sirit::ValidId(offset)) {
+ return;
+ }
+
+ Id result_type{};
+ switch (info.type) {
+ case TextureType::Buffer:
+ case TextureType::Color1D: {
+ result_type = ctx.U32[1];
+ break;
+ }
+ case TextureType::ColorArray1D:
+ offset = ctx.OpCompositeConstruct(ctx.U32[2], offset, ctx.u32_zero_value);
+ [[fallthrough]];
+ case TextureType::Color2D:
+ case TextureType::Color2DRect: {
+ result_type = ctx.U32[2];
+ break;
+ }
+ case TextureType::ColorArray2D:
+ offset = ctx.OpCompositeConstruct(ctx.U32[3], ctx.OpCompositeExtract(ctx.U32[1], coords, 0),
+ ctx.OpCompositeExtract(ctx.U32[1], coords, 1),
+ ctx.u32_zero_value);
+ [[fallthrough]];
+ case TextureType::Color3D: {
+ result_type = ctx.U32[3];
+ break;
+ }
+ case TextureType::ColorCube:
+ case TextureType::ColorArrayCube:
+ return;
+ }
+ coords = ctx.OpIAdd(result_type, coords, offset);
+}
} // Anonymous namespace
Id EmitBindlessImageSampleImplicitLod(EmitContext&) {
@@ -494,9 +530,10 @@ Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index,
operands.Span());
}
-Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
- const IR::Value& offset, Id lod, Id ms) {
+Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id offset,
+ Id lod, Id ms) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
+ AddOffsetToCoordinates(ctx, info, coords, offset);
if (info.type == TextureType::Buffer) {
lod = Id{};
}
@@ -504,7 +541,7 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c
// This image is multisampled, lod must be implicit
lod = Id{};
}
- const ImageOperands operands(ctx, offset, lod, ms);
+ const ImageOperands operands(lod, ms);
return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4],
TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span());
}
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
index 08fcabd58..5c01b1012 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
@@ -537,8 +537,8 @@ Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id
const IR::Value& offset, const IR::Value& offset2);
Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
const IR::Value& offset, const IR::Value& offset2, Id dref);
-Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
- const IR::Value& offset, Id lod, Id ms);
+Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id offset,
+ Id lod, Id ms);
Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod,
const IR::Value& skip_mips);
Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords);
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 6d3d933c5..296c90e85 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -1130,7 +1130,7 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index) {
channel_state->vertex_buffers[index] = NULL_BINDING;
return;
}
- if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end)) {
+ if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || size >= 64_MiB) {
size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size));
}
const BufferId buffer_id = FindBuffer(*device_addr, size);
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index a94e1f043..0d47b032c 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -291,7 +291,9 @@ u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) {
}
void Maxwell3D::ConsumeSinkImpl() {
- SCOPE_EXIT({ method_sink.clear(); });
+ SCOPE_EXIT {
+ method_sink.clear();
+ };
const auto control = shadow_state.shadow_ram_control;
if (control == Regs::ShadowRamControl::Track ||
control == Regs::ShadowRamControl::TrackWithFilter) {
diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h
index c3eda6893..2135f1f2d 100644
--- a/src/video_core/fence_manager.h
+++ b/src/video_core/fence_manager.h
@@ -197,7 +197,9 @@ private:
MicroProfileOnThreadCreate(name.c_str());
// Cleanup
- SCOPE_EXIT({ MicroProfileOnThreadExit(); });
+ SCOPE_EXIT {
+ MicroProfileOnThreadExit();
+ };
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp
index 58d8110b8..477e11457 100644
--- a/src/video_core/gpu_thread.cpp
+++ b/src/video_core/gpu_thread.cpp
@@ -22,7 +22,9 @@ static void RunThread(std::stop_token stop_token, Core::System& system,
Tegra::Control::Scheduler& scheduler, SynchState& state) {
std::string name = "GPU";
MicroProfileOnThreadCreate(name.c_str());
- SCOPE_EXIT({ MicroProfileOnThreadExit(); });
+ SCOPE_EXIT {
+ MicroProfileOnThreadExit();
+ };
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
diff --git a/src/video_core/host1x/ffmpeg/ffmpeg.cpp b/src/video_core/host1x/ffmpeg/ffmpeg.cpp
index 96686da59..1003cd38d 100644
--- a/src/video_core/host1x/ffmpeg/ffmpeg.cpp
+++ b/src/video_core/host1x/ffmpeg/ffmpeg.cpp
@@ -273,10 +273,10 @@ DeinterlaceFilter::DeinterlaceFilter(const Frame& frame) {
const AVFilter* buffer_sink = avfilter_get_by_name("buffersink");
AVFilterInOut* inputs = avfilter_inout_alloc();
AVFilterInOut* outputs = avfilter_inout_alloc();
- SCOPE_EXIT({
+ SCOPE_EXIT {
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);
- });
+ };
// Don't know how to get the accurate time_base but it doesn't matter for yadif filter
// so just use 1/1 to make buffer filter happy
diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp
index 46e853e04..fb529f88b 100644
--- a/src/video_core/macro/macro_hle.cpp
+++ b/src/video_core/macro/macro_hle.cpp
@@ -92,12 +92,12 @@ public:
private:
void Fallback(const std::vector<u32>& parameters) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (extended) {
maxwell3d.engine_state = Maxwell3D::EngineHint::None;
maxwell3d.replace_table.clear();
}
- });
+ };
maxwell3d.RefreshParameters();
const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]);
@@ -281,12 +281,12 @@ public:
private:
void Fallback(const std::vector<u32>& parameters) {
- SCOPE_EXIT({
+ SCOPE_EXIT {
// Clean everything.
maxwell3d.regs.vertex_id_base = 0x0;
maxwell3d.engine_state = Maxwell3D::EngineHint::None;
maxwell3d.replace_table.clear();
- });
+ };
maxwell3d.RefreshParameters();
const u32 start_indirect = parameters[0];
const u32 end_indirect = parameters[1];
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index b42fb110c..16af8e6bd 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -230,7 +230,9 @@ template <typename Func>
void RasterizerOpenGL::PrepareDraw(bool is_indexed, Func&& draw_func) {
MICROPROFILE_SCOPE(OpenGL_Drawing);
- SCOPE_EXIT({ gpu.TickWork(); });
+ SCOPE_EXIT {
+ gpu.TickWork();
+ };
gpu_memory->FlushCaching();
GraphicsPipeline* const pipeline{shader_cache.CurrentGraphicsPipeline()};
@@ -355,7 +357,9 @@ void RasterizerOpenGL::DrawIndirect() {
void RasterizerOpenGL::DrawTexture() {
MICROPROFILE_SCOPE(OpenGL_Drawing);
- SCOPE_EXIT({ gpu.TickWork(); });
+ SCOPE_EXIT {
+ gpu.TickWork();
+ };
texture_cache.SynchronizeGraphicsDescriptors();
texture_cache.UpdateRenderTargets(false);
diff --git a/src/video_core/renderer_vulkan/present/layer.cpp b/src/video_core/renderer_vulkan/present/layer.cpp
index 3847a9a13..4e41afe5b 100644
--- a/src/video_core/renderer_vulkan/present/layer.cpp
+++ b/src/video_core/renderer_vulkan/present/layer.cpp
@@ -82,7 +82,9 @@ void Layer::ConfigureDraw(PresentPushConstants* out_push_constants,
// Finish any pending renderpass
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Wait(resource_ticks[image_index]);
- SCOPE_EXIT({ resource_ticks[image_index] = scheduler.CurrentTick(); });
+ SCOPE_EXIT {
+ resource_ticks[image_index] = scheduler.CurrentTick();
+ };
if (!use_accelerated) {
UpdateRawImage(framebuffer, image_index);
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index d50417116..c553f5b3d 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -144,7 +144,9 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
return;
}
- SCOPE_EXIT({ render_window.OnFrameDisplayed(); });
+ SCOPE_EXIT {
+ render_window.OnFrameDisplayed();
+ };
RenderAppletCaptureLayer(framebuffers);
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index aa0a027bb..74f9f099e 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -196,7 +196,9 @@ template <typename Func>
void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
MICROPROFILE_SCOPE(Vulkan_Drawing);
- SCOPE_EXIT({ gpu.TickWork(); });
+ SCOPE_EXIT {
+ gpu.TickWork();
+ };
FlushWork();
gpu_memory->FlushCaching();
@@ -288,7 +290,9 @@ void RasterizerVulkan::DrawIndirect() {
void RasterizerVulkan::DrawTexture() {
MICROPROFILE_SCOPE(Vulkan_Drawing);
- SCOPE_EXIT({ gpu.TickWork(); });
+ SCOPE_EXIT {
+ gpu.TickWork();
+ };
FlushWork();
query_cache.NotifySegment(true);
diff --git a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp
index 5fa0d9620..f41c3e506 100644
--- a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp
+++ b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp
@@ -116,7 +116,9 @@ void NsightAftermathTracker::OnGpuCrashDumpCallback(const void* gpu_crash_dump,
LOG_ERROR(Render_Vulkan, "Failed to create decoder");
return;
}
- SCOPE_EXIT({ GFSDK_Aftermath_GpuCrashDump_DestroyDecoder(decoder); });
+ SCOPE_EXIT {
+ GFSDK_Aftermath_GpuCrashDump_DestroyDecoder(decoder);
+ };
u32 json_size = 0;
if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_GpuCrashDump_GenerateJSON(
diff --git a/src/yuzu/configuration/qt_config.cpp b/src/yuzu/configuration/qt_config.cpp
index 1051031f2..37951b9c8 100644
--- a/src/yuzu/configuration/qt_config.cpp
+++ b/src/yuzu/configuration/qt_config.cpp
@@ -90,6 +90,7 @@ void QtConfig::ReadQtPlayerValues(const std::size_t player_index) {
if (profile_name.empty()) {
// Use the global input config
player = Settings::values.players.GetValue(true)[player_index];
+ player.profile_name = "";
return;
}
}
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index dfa50006a..236642fb9 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -646,10 +646,10 @@ void GMainWindow::AmiiboSettingsShowDialog(const Core::Frontend::CabinetParamete
std::shared_ptr<Service::NFC::NfcDevice> nfp_device) {
cabinet_applet =
new QtAmiiboSettingsDialog(this, parameters, input_subsystem.get(), nfp_device);
- SCOPE_EXIT({
+ SCOPE_EXIT {
cabinet_applet->deleteLater();
cabinet_applet = nullptr;
- });
+ };
cabinet_applet->setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |
Qt::WindowTitleHint | Qt::WindowSystemMenuHint);
@@ -673,10 +673,10 @@ void GMainWindow::ControllerSelectorReconfigureControllers(
const Core::Frontend::ControllerParameters& parameters) {
controller_applet =
new QtControllerSelectorDialog(this, parameters, input_subsystem.get(), *system);
- SCOPE_EXIT({
+ SCOPE_EXIT {
controller_applet->deleteLater();
controller_applet = nullptr;
- });
+ };
controller_applet->setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint |
Qt::WindowStaysOnTopHint | Qt::WindowTitleHint |
@@ -703,10 +703,10 @@ void GMainWindow::ControllerSelectorRequestExit() {
void GMainWindow::ProfileSelectorSelectProfile(
const Core::Frontend::ProfileSelectParameters& parameters) {
profile_select_applet = new QtProfileSelectionDialog(*system, this, parameters);
- SCOPE_EXIT({
+ SCOPE_EXIT {
profile_select_applet->deleteLater();
profile_select_applet = nullptr;
- });
+ };
profile_select_applet->setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint |
Qt::WindowStaysOnTopHint | Qt::WindowTitleHint |
@@ -1603,6 +1603,7 @@ void GMainWindow::ConnectMenuEvents() {
// Help
connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder);
connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents);
+ connect_menu(ui->action_Install_Firmware, &GMainWindow::OnInstallFirmware);
connect_menu(ui->action_About, &GMainWindow::OnAbout);
}
@@ -1631,6 +1632,8 @@ void GMainWindow::UpdateMenuState() {
action->setEnabled(emulation_running);
}
+ ui->action_Install_Firmware->setEnabled(!emulation_running);
+
for (QAction* action : applet_actions) {
action->setEnabled(is_firmware_available && !emulation_running);
}
@@ -2882,17 +2885,19 @@ bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path,
LOG_ERROR(Frontend, "CoInitialize failed");
return false;
}
- SCOPE_EXIT({ CoUninitialize(); });
+ SCOPE_EXIT {
+ CoUninitialize();
+ };
IShellLinkW* ps1 = nullptr;
IPersistFile* persist_file = nullptr;
- SCOPE_EXIT({
+ SCOPE_EXIT {
if (persist_file != nullptr) {
persist_file->Release();
}
if (ps1 != nullptr) {
ps1->Release();
}
- });
+ };
HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
reinterpret_cast<void**>(&ps1));
if (FAILED(hres)) {
@@ -3517,10 +3522,10 @@ void GMainWindow::OnSaveConfig() {
void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) {
error_applet = new OverlayDialog(render_window, *system, error_code, error_text, QString{},
tr("OK"), Qt::AlignLeft | Qt::AlignVCenter);
- SCOPE_EXIT({
+ SCOPE_EXIT {
error_applet->deleteLater();
error_applet = nullptr;
- });
+ };
error_applet->exec();
emit ErrorDisplayFinished();
@@ -4150,6 +4155,146 @@ void GMainWindow::OnVerifyInstalledContents() {
}
}
+void GMainWindow::OnInstallFirmware() {
+ // Don't do this while emulation is running, that'd probably be a bad idea.
+ if (emu_thread != nullptr && emu_thread->IsRunning()) {
+ return;
+ }
+
+ // Check for installed keys, error out, suggest restart?
+ if (!ContentManager::AreKeysPresent()) {
+ QMessageBox::information(
+ this, tr("Keys not installed"),
+ tr("Install decryption keys and restart yuzu before attempting to install firmware."));
+ return;
+ }
+
+ QString firmware_source_location =
+ QFileDialog::getExistingDirectory(this, tr("Select Dumped Firmware Source Location"),
+ QString::fromStdString(""), QFileDialog::ShowDirsOnly);
+ if (firmware_source_location.isEmpty()) {
+ return;
+ }
+
+ QProgressDialog progress(tr("Installing Firmware..."), tr("Cancel"), 0, 100, this);
+ progress.setWindowModality(Qt::WindowModal);
+ progress.setMinimumDuration(100);
+ progress.setAutoClose(false);
+ progress.setAutoReset(false);
+ progress.show();
+
+ // Declare progress callback.
+ auto QtProgressCallback = [&](size_t total_size, size_t processed_size) {
+ progress.setValue(static_cast<int>((processed_size * 100) / total_size));
+ return progress.wasCanceled();
+ };
+
+ LOG_INFO(Frontend, "Installing firmware from {}", firmware_source_location.toStdString());
+
+ // Check for a reasonable number of .nca files (don't hardcode them, just see if there's some in
+ // there.)
+ std::filesystem::path firmware_source_path = firmware_source_location.toStdString();
+ if (!Common::FS::IsDir(firmware_source_path)) {
+ progress.close();
+ return;
+ }
+
+ std::vector<std::filesystem::path> out;
+ const Common::FS::DirEntryCallable callback =
+ [&out](const std::filesystem::directory_entry& entry) {
+ if (entry.path().has_extension() && entry.path().extension() == ".nca")
+ out.emplace_back(entry.path());
+
+ return true;
+ };
+
+ QtProgressCallback(100, 10);
+
+ Common::FS::IterateDirEntries(firmware_source_path, callback, Common::FS::DirEntryFilter::File);
+ if (out.size() <= 0) {
+ progress.close();
+ QMessageBox::warning(this, tr("Firmware install failed"),
+ tr("Unable to locate potential firmware NCA files"));
+ return;
+ }
+
+ // Locate and erase the content of nand/system/Content/registered/*.nca, if any.
+ auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory();
+ if (!sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) {
+ progress.close();
+ QMessageBox::critical(this, tr("Firmware install failed"),
+ tr("Failed to delete one or more firmware file."));
+ return;
+ }
+
+ LOG_INFO(Frontend,
+ "Cleaned nand/system/Content/registered folder in preparation for new firmware.");
+
+ QtProgressCallback(100, 20);
+
+ auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered");
+
+ bool success = true;
+ bool cancelled = false;
+ int i = 0;
+ for (const auto& firmware_src_path : out) {
+ i++;
+ auto firmware_src_vfile =
+ vfs->OpenFile(firmware_src_path.generic_string(), FileSys::OpenMode::Read);
+ auto firmware_dst_vfile =
+ firmware_vdir->CreateFileRelative(firmware_src_path.filename().string());
+
+ if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) {
+ LOG_ERROR(Frontend, "Failed to copy firmware file {} to {} in registered folder!",
+ firmware_src_path.generic_string(), firmware_src_path.filename().string());
+ success = false;
+ }
+
+ if (QtProgressCallback(100, 20 + (int)(((float)(i) / (float)out.size()) * 70.0))) {
+ success = false;
+ cancelled = true;
+ break;
+ }
+ }
+
+ if (!success && !cancelled) {
+ progress.close();
+ QMessageBox::critical(this, tr("Firmware install failed"),
+ tr("One or more firmware files failed to copy into NAND."));
+ return;
+ } else if (cancelled) {
+ progress.close();
+ QMessageBox::warning(this, tr("Firmware install failed"),
+ tr("Firmware installation cancelled, firmware may be in bad state, "
+ "restart yuzu or re-install firmware."));
+ return;
+ }
+
+ // Re-scan VFS for the newly placed firmware files.
+ system->GetFileSystemController().CreateFactories(*vfs);
+
+ auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) {
+ progress.setValue(90 + static_cast<int>((processed_size * 10) / total_size));
+ return progress.wasCanceled();
+ };
+
+ auto result =
+ ContentManager::VerifyInstalledContents(*system, *provider, VerifyFirmwareCallback, true);
+
+ if (result.size() > 0) {
+ const auto failed_names =
+ QString::fromStdString(fmt::format("{}", fmt::join(result, "\n")));
+ progress.close();
+ QMessageBox::critical(
+ this, tr("Firmware integrity verification failed!"),
+ tr("Verification failed for the following files:\n\n%1").arg(failed_names));
+ return;
+ }
+
+ progress.close();
+ OnCheckFirmwareDecryption();
+}
+
void GMainWindow::OnAbout() {
AboutDialog aboutDialog(this);
aboutDialog.exec();
@@ -5049,7 +5194,9 @@ int main(int argc, char* argv[]) {
Common::DetachedTasks detached_tasks;
MicroProfileOnThreadCreate("Frontend");
- SCOPE_EXIT({ MicroProfileShutdown(); });
+ SCOPE_EXIT {
+ MicroProfileShutdown();
+ };
Common::ConfigureNvidiaEnvironmentFlags();
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index aba61e388..1f0e35c67 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -380,6 +380,7 @@ private slots:
void OnLoadAmiibo();
void OnOpenYuzuFolder();
void OnVerifyInstalledContents();
+ void OnInstallFirmware();
void OnAbout();
void OnToggleFilterBar();
void OnToggleStatusBar();
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 6a6b0821f..6ff444a22 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -25,7 +25,16 @@
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
- <property name="margin" stdset="0">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
<number>0</number>
</property>
</layout>
@@ -156,8 +165,8 @@
<addaction name="separator"/>
<addaction name="action_Configure_Tas"/>
</widget>
- <addaction name="action_Rederive"/>
<addaction name="action_Verify_installed_contents"/>
+ <addaction name="action_Install_Firmware"/>
<addaction name="separator"/>
<addaction name="menu_cabinet_applet"/>
<addaction name="action_Load_Album"/>
@@ -455,6 +464,11 @@
<string>Open &amp;Controller Menu</string>
</property>
</action>
+ <action name="action_Install_Firmware">
+ <property name="text">
+ <string>Install Firmware</string>
+ </property>
+ </action>
</widget>
<resources>
<include location="yuzu.qrc"/>
diff --git a/src/yuzu_cmd/sdl_config.cpp b/src/yuzu_cmd/sdl_config.cpp
index 995114510..6e0f254b6 100644
--- a/src/yuzu_cmd/sdl_config.cpp
+++ b/src/yuzu_cmd/sdl_config.cpp
@@ -103,6 +103,7 @@ void SdlConfig::ReadSdlPlayerValues(const std::size_t player_index) {
if (profile_name.empty()) {
// Use the global input config
player = Settings::values.players.GetValue(true)[player_index];
+ player.profile_name = "";
return;
}
}
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 3b321dad1..8a8cdbc44 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -327,7 +327,9 @@ int main(int argc, char** argv) {
#endif
MicroProfileOnThreadCreate("EmuThread");
- SCOPE_EXIT({ MicroProfileShutdown(); });
+ SCOPE_EXIT {
+ MicroProfileShutdown();
+ };
Common::ConfigureNvidiaEnvironmentFlags();